Skip to content
VLSI Mentor

USB · Module 6

Enumeration Timing

The numbers enumeration runs on and why each one exists — debounce, reset duration and recovery, the SetAddress recovery interval — plus the layered-timeout structure that decides when a slow device becomes a broken one.

Everything in this module so far has been about order. Chapter 6.6 Figure 1's vertical axis was explicitly not time, and every duration was deferred to here.

This chapter supplies them. It is more interesting than a table for two reasons.

First, each number is derivable. Not one is arbitrary. Each exists because of a physical or logical fact this module has already established, and knowing the fact makes the number memorable in a way that rote learning never does.

Second, the timeouts are layered. A host's patience at one level is bounded by its patience at the level above, and that structure — not any individual value — is what decides when a device that is merely slow becomes a device that is broken. That line is a design constraint every device controller has to meet.

1. The Numbers

The values enumeration runs on, each with the chapter that established the reason for it.

IntervalValueWho waitsWhy it exists
Attach debounce100 msHub, before reportingA mechanical connector does not connect cleanly — Chapter 6.1
Reset assertionat least 10 msHost, driving the busMust be unmistakable to every device at every speed — Chapter 6.2
Device reset detectionat least 2.5 µsDevice, before believing itMust not mistake ordinary idle for a reset — Chapter 3.7
Reset recovery10 msHost, after releasing resetThe device has internal work to do before it can answer
SetAddress recovery2 msHost, after the address commitsThe device must switch address before being addressed again — Chapter 6.3

Read the who waits column. Four of the five are the host or hub waiting, and only one is the device. That asymmetry is Chapter 2.6's host-centric model appearing in the time domain: the host holds the schedule, so the host is the one that must build in tolerance. A device that needs time gets it because the host has already decided to allow it, not because the device asks.

2. Deriving Each One

Memorising the table is worth little. Deriving it is worth a great deal, because the reasoning transfers to protocols whose numbers you do not know.

Attach debounce — 100 ms

The fact. A connector is a mechanical object. Contacts bounce, and during the bounce the electrical indication of a connection appears and disappears repeatedly.

The consequence. Chapter 6.1 established that acting on the first indication means enumerating a device that is not yet reliably connected, and that the reset which follows may land mid-bounce.

Why this magnitude. It is set by mechanics, not electronics — the settling time of a physical connector being pushed in by a human hand. Milliseconds is the right order for that, and 100 ms is generous enough to cover a slow or awkward insertion.

The tell that this is the right reading: it is by far the largest number in the table, and it is the only one whose cause is not electrical.

Reset assertion — at least 10 ms

The fact. Reset is signalled as a bus condition, and Chapter 6.2 established it must be unambiguous to every device that could be attached, regardless of speed, and regardless of what the device was doing.

Why this magnitude. It must be long enough that no ordinary bus activity could be mistaken for it, by any device, with any tolerance on its internal timing. That means comfortably longer than any legitimate idle gap in normal traffic — and comfortably is doing real work there, because the margin has to hold for the cheapest and least accurate device on the bus.

Note the direction of the bound. This is a minimum on the host. Longer is always safe; shorter is not. Every number in this table that constrains a driver of an event is a minimum, and every number that constrains a waiter is also a minimum. Nothing here is a maximum, which is §3's subject.

Device reset detection — at least 2.5 µs

The fact. The device must decide that the bus condition it is seeing is a reset rather than an idle gap between packets.

Why this magnitude, and why so much smaller than the reset itself. The two numbers answer different questions. The host's 10 ms is how long to make it unmistakable to everyone; the device's 2.5 µs is how long before I am entitled to believe it. The gap between them — nearly four orders of magnitude — is pure margin, and it is what lets devices with crude internal timing still detect reset reliably.

The design consequence, which is the point of including it: a device controller must not react at the first sample. Chapter 3.7 owns the qualification logic; what matters here is that the threshold exists, and that a controller which reacts too quickly will reset itself on ordinary traffic gaps.

Reset recovery — 10 ms

The fact. A device coming out of reset has internal work to do before it can respond: Chapter 6.2 showed that reset returns protocol state to defined values, and doing so is not instantaneous in real hardware.

Why this magnitude. It is generous by the standards of the work involved, and deliberately so. The alternative to a generous recovery interval is requiring every device — including the cheapest — to be ready within a tight bound, which would raise the cost of every device to save the host a few milliseconds once per attach.

The trade in one sentence: the host waits longer so that devices can be simpler, which is the same trade Chapter 2.6 identified as the architecture's central bargain.

SetAddress recovery — 2 ms

The fact. Chapter 6.3 established that the device switches address after the SetAddress transfer completes, and that the switch involves real work in the controller.

Why it exists at all. Without it the host could address the device before the device had finished becoming addressable, and the request would be lost — a failure that looks exactly like a broken device.

Why it is so much shorter than reset recovery. The work is far smaller. Reset recovery covers a device re-establishing its entire protocol state; SetAddress recovery covers a single register commit. The relative sizes of these two numbers are a direct readout of the relative sizes of the two jobs, which is why 2 ms against 10 ms is worth remembering as a ratio rather than as two isolated facts.

Enumeration intervals, in order

8 cycles
A sequence of enumeration timing intervals shown in order. First the attach indication becomes unstable and the hub debounces for one hundred milliseconds. Then the host asserts a bus reset for at least ten milliseconds, during which the device requires at least two and a half microseconds to be entitled to believe the reset is genuine. After the reset is released the host allows ten milliseconds of reset recovery before addressing the device. After the SetAddress transfer commits, the host allows two milliseconds of recovery before addressing the device at its new address. The figure shows ordinal widths only and is not to scale, since the true ratio between one hundred milliseconds and two and a half microseconds cannot be drawn on a single axis.debounce — mechanical settlingdebounce — mechanicalsettlingdevice believes reset after 2.5 µsdevice believes reset after2.5 µsreset recovery — device rebuilding statereset recovery — devicerebuilding stateSetAddress recovery — register commitSetAddress recovery —register commitattachXhub debounce--100ms------------bus_resethost waits----10ms+--10ms--2ms--dev_stateATTATTATTDEFDEFDEFADDRADDRhost may talkt0t1t2t3t4t5t6t7
Figure 1 — the intervals in sequence. Widths are ordinal only: the true ratio between 100 ms and 2.5 µs cannot be drawn on one axis.

3. Layered Timeouts

Now the structural point, which matters more than any individual value.

None of §1's numbers is a timeout. Every one is a wait — a minimum the host observes before proceeding. Timeouts are a separate mechanism answering a different question: how long before the host gives up?

And they nest. A single transfer has a bound. The enumeration step that transfer belongs to has a bound. The whole enumeration attempt has a bound. Each outer bound must be at least as large as what it contains, or the outer one fires first and the inner one is unreachable — a detail that sounds obvious and is a real source of bugs in host stacks and in test environments alike.

A nested diagram of enumeration timeouts. The outermost level is the whole enumeration attempt, which contains the per-step bound for a step such as the descriptor walk, which in turn contains the per-transfer bound for a single control transfer. An arrow shows that exhausting the innermost bound triggers a retry of the transfer, exhausting the step bound triggers a restart of enumeration by bus reset, and exhausting the outermost bound causes the host to declare the device failed.Enumeration attemptattach to configuredEnumeration stepe.g. the descriptor walkSingle transferone request and itsresponseDeclare device failedreport to softwareReset and restartthe flow's one recoveryedgeRetry the transfercheapest recoverycontainscontainsbound exhaustedbound exhaustedbound exhausted12
Figure 2 — patience nests. An inner bound that exceeds its container is a bound that can never fire.

Each layer's exhaustion has a different response, and the escalation is the design.

  • A transfer that times out is retried. Cheap, local, and most transient problems end here.
  • A step that exhausts its bound despite retries means retrying is not working, so the host escalates to the flow's one recovery edge: reset and restartChapter 6.6 §2's unconditional edge, now with a reason to be taken.
  • An attempt that exhausts its bound means restarting is not working either, and the host declares the device failed.

That escalation answers the question this chapter opened with. A slow device and a broken device are distinguished by which layer it exhausts: a device that is merely slow finishes inside the step bound after a retry or two, while a device that repeatedly exhausts the step bound is indistinguishable from broken — and must be treated as such, because a host that waits forever for one device has stopped serving the others.

A sequence diagram of timeout escalation during enumeration. The host issues a request and the device does not respond, so the per-transfer bound expires and the host retries the same transfer. The device again does not respond, and after further retries the step bound expires, so the host asserts a bus reset and restarts enumeration from the beginning. If the restarted attempt also fails to progress, the whole-attempt bound expires and the host reports the device as failed to software.Escalation when a device does not respondHostDeviceSoftwarerequesttransfer boundexpiresretry — cheapestrecoverystep bound expiresdespite retriesbus reset — restartenumerationattempt boundexpiresdevice failed
Figure 3 — the escalation ladder. Each rung is taken only because the rung below it failed to help.

4. The Escalation, as RTL

The layering of §3 is the chapter's real content, and it is small enough to build exactly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_enum_timeout
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models the
// LAYERED TIMEOUT STRUCTURE of section 3 -- three nested bounds and the
// escalation between them -- and no other USB mechanism.
//
// WHAT IT MODELS. A per-transfer bound whose expiry retries, a per-step
// bound whose expiry resets and restarts, and a whole-attempt bound whose
// expiry declares failure. It also models the nesting REQUIREMENT itself:
// an inner bound that is not smaller than its container can never fire,
// and the module reports that condition rather than hiding it.
//
// WHAT IT DOES NOT MODEL. The WAITS of section 1, which are a different
// mechanism entirely -- minimums observed before proceeding, not upper
// bounds. Also: transfer mechanics (Module 13), packets (Modules 11-12),
// any real duration (the bounds here are in clock cycles, scaled down so
// a simulation is short), and the host's scheduling of other devices.
//
// NOTE ON SIDE. This is host-side logic. It appears in a device-oriented
// module because a device controller must be designed against it: section 7
// is about meeting these bounds, and doing that requires knowing their shape.
// ─────────────────────────────────────────────────────────────────────────
package usb_timeout_pkg;
  typedef enum logic [1:0] {
    ESC_NONE    = 2'b00,   // nothing has expired
    ESC_RETRY   = 2'b01,   // retry the transfer
    ESC_RESTART = 2'b10,   // bus reset and start enumeration again
    ESC_FAILED  = 2'b11    // give up; report to software
  } escalation_e;
endpackage

module usb_enum_timeout
  import usb_timeout_pkg::*;
#(
  // Cycle counts, deliberately small. The RELATIONSHIP between them is what
  // this module is about; the absolute values are a simulation convenience.
  parameter int unsigned XFER_LIMIT    = 8,
  parameter int unsigned STEP_LIMIT    = 40,
  parameter int unsigned ATTEMPT_LIMIT = 200
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        attempt_start,   // a fresh enumeration attempt begins
  input  logic        xfer_start,      // a transfer has been issued
  input  logic        xfer_ok,         // it completed; clears the inner bound
  input  logic        step_ok,         // a whole step completed

  output escalation_e escalation,      // 1-cycle pulse-coded action
  output logic        attempt_active,

  // Held high while the parameters make an inner bound unable to fire.
  // Reporting this is the point: section 3's nesting rule is a REQUIREMENT,
  // and a silently mis-parameterised supervisor disables its own cheapest
  // recovery without any symptom at the point of the mistake.
  output logic        nesting_violation
);

  // The nesting rule of section 3, checked structurally rather than trusted.
  // Note the comparison is strict: an inner bound EQUAL to its container
  // races with it, and which fires first is then an accident of the
  // implementation rather than a decision.
  assign nesting_violation = !((XFER_LIMIT < STEP_LIMIT)
                            && (STEP_LIMIT < ATTEMPT_LIMIT));

  logic [31:0] xfer_cnt, step_cnt, attempt_cnt;
  logic        xfer_armed;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      xfer_cnt       <= '0;
      step_cnt       <= '0;
      attempt_cnt    <= '0;
      xfer_armed     <= 1'b0;
      attempt_active <= 1'b0;
      escalation     <= ESC_NONE;
    end else begin
      escalation <= ESC_NONE;            // single-cycle

      if (attempt_start) begin
        attempt_active <= 1'b1;
        attempt_cnt    <= '0;
        step_cnt       <= '0;
        xfer_cnt       <= '0;
        xfer_armed     <= 1'b0;
      end else if (attempt_active) begin

        attempt_cnt <= attempt_cnt + 1;
        step_cnt    <= step_cnt + 1;
        if (xfer_armed) xfer_cnt <= xfer_cnt + 1;

        if (xfer_start) begin
          xfer_armed <= 1'b1;
          xfer_cnt   <= '0;
        end else if (xfer_ok) begin
          xfer_armed <= 1'b0;            // inner bound disarmed by success
        end

        if (step_ok) begin
          step_cnt   <= '0;
          xfer_armed <= 1'b0;
        end

        // ── Escalation, OUTERMOST FIRST ─────────────────────────────────
        // Checking the innermost first lets an inner action win the cycle
        // in which the attempt should have been abandoned, so the host
        // drives one more reset at a device it is giving up on. Section 6
        // measures that, and also measures why it is only one cycle wide:
        // the comparisons below are `>=`, so an expiry that loses a cycle
        // fires on the next one instead of being lost. Both choices matter,
        // and together they matter much more than either does alone.
        if (attempt_cnt >= ATTEMPT_LIMIT) begin
          escalation     <= ESC_FAILED;
          attempt_active <= 1'b0;
        end else if (step_cnt >= STEP_LIMIT) begin
          escalation <= ESC_RESTART;
          step_cnt   <= '0;
          xfer_cnt   <= '0;
          xfer_armed <= 1'b0;
        end else if (xfer_armed && (xfer_cnt >= XFER_LIMIT)) begin
          escalation <= ESC_RETRY;
          xfer_cnt   <= '0;
        end
      end
    end
  end

endmodule

What it models. Three nested bounds, the escalation between them, and the nesting requirement itself as a reported condition.

Why this hardware exists. Because §3's escalation ladder is genuinely a state machine with three counters, and because the nesting rule is the kind of requirement that is easy to state, easy to violate in a parameter file, and completely silent when violated.

Inputs. Clock and reset; an attempt start; transfer issue and success; and step success.

State retained. Three counters, an armed flag for the innermost bound, and whether an attempt is in progress.

Outputs. A pulse-coded escalation action, an attempt-active indication, and the nesting-violation report.

Hardware implied. Three counters, three comparators against parameters, and a small priority encoder.

Assumptions. That xfer_start, xfer_ok and step_ok are qualified single-cycle pulses in this clock domain, and that the parameters are set from real host bounds scaled to the clock.

Deliberately omits. The §1 waits entirely — they are a different mechanism and mixing them into a timeout supervisor is a category error the module is written to avoid — plus transfer mechanics, packets, real durations and multi-device scheduling.

Two decisions inside it carry the teaching:

  • nesting_violation is an output, not an assertion. §3's rule is a requirement on the parameters, and a supervisor whose parameters disable its own cheapest recovery has no symptom at the point of the mistake. Making it a signal means a mis-parameterised instance announces itself.
  • The escalation branches are ordered outermost-first. The comment says why: innermost-first would let a retry be issued in the same cycle the attempt was abandoned, so the host sends traffic to a device it has just declared failed. §6 measures it.

5. The Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Assertions for usb_enum_timeout.
//
// Classification: TEACHING ASSERTIONS about the escalation structure.
// They are not a USB compliance suite, and they say nothing about the
// section 1 waits, which this module does not implement.
// ─────────────────────────────────────────────────────────────────────────

// T1 -- ESCALATION ORDER. Nothing may be escalated after the attempt has
// been abandoned. Note what section 6 found: this property does NOT catch
// the branch-ordering mutant, because that mutant acts one cycle BEFORE the
// failure rather than after it. The property is still right; it simply
// bounds a different edge than intuition suggests, which is worth knowing
// before relying on it.
property p_no_action_after_failure;
  @(posedge clk) disable iff (!rst_n)
    (escalation == ESC_FAILED) |=> (escalation == ESC_NONE);
endproperty
assert property (p_no_action_after_failure);

// T2 -- a retry and a failure are mutually exclusive in the same cycle.
// Implied by the encoding here, and worth asserting because a design that
// drives the three actions as separate signals can emit two at once.
property p_single_action;
  @(posedge clk) disable iff (!rst_n)
    (escalation != ESC_NONE) |-> attempt_active || (escalation == ESC_FAILED);
endproperty
assert property (p_single_action);

// T3 -- NESTING. If the parameters are sane, the innermost bound must
// actually be reachable: a transfer that never completes must eventually
// produce a retry rather than going straight to a restart.
property p_inner_bound_is_reachable;
  @(posedge clk) disable iff (!rst_n || nesting_violation)
    (xfer_start && attempt_active) |-> ##[1:$] (escalation != ESC_NONE);
endproperty
assert property (p_inner_bound_is_reachable);

// T4 -- a successful transfer disarms the inner bound, so no retry may be
// issued for a transfer that already completed.
property p_success_disarms;
  @(posedge clk) disable iff (!rst_n)
    (xfer_ok && !xfer_start) |=> (escalation != ESC_RETRY);
endproperty
assert property (p_success_disarms);

// T5 -- the parameters must nest. Asserted, not assumed, because this is
// the requirement a parameter file silently breaks.
property p_parameters_nest;
  @(posedge clk) disable iff (!rst_n)
    !nesting_violation;
endproperty
assert property (p_parameters_nest);

T3 is the interesting one, because it is a property about reachability rather than about correctness of a value. Most assertions say this must not happen; T3 says this must eventually happen, and it is the only kind that catches a bound which has been configured out of existence.

T5 looks like it is asserting a constant, and it is — a constant derived from parameters. That is exactly why it belongs in the assertion set rather than in a comment: it fails at elaboration-time-ish, on the first cycle, in any instance whose parameters are wrong, which is far earlier and far louder than the silent loss of retry behaviour it prevents.

6. Mutation Test

Two mutants, both about the structure rather than the values.

T-M1 — escalate innermost-first

Reverse the branch order, which reads more naturally and is how most people write it first.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (xfer_armed && (xfer_cnt >= XFER_LIMIT)) begin ... end
else if (step_cnt >= STEP_LIMIT) begin ... end
else if (attempt_cnt >= ATTEMPT_LIMIT) begin ... end   // MUTANT T-M1

Result: it survived. Against a device that never responds, the mutant produced the same escalation ladder as the correct design — four retries, four restarts, one failure — and passed every assertion. The defect that was predicted did not appear.

That is worth investigating rather than shrugging at, and doing so found something the design had not been given credit for.

Why it survived. The outer conditions are written with >=, not ==. When an inner action wins a cycle, the outer condition is still true on the next cycle, so it fires one cycle late instead of never. The ordering hazard is real and the >= absorbs it.

Then the bounds were made to actually collide. With the attempt bound set to land exactly on a restart instant, the difference appears:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ATTEMPT_LIMIT on a restart instant (a genuine collision)
  golden    retries=4  restarts=3  failed=1  attempt_active=0
  T-M1      retries=4  restarts=4  failed=1  attempt_active=0
                       ^^^^^^^^^^ one EXTRA restart

ATTEMPT_LIMIT away from any collision
  golden    retries=4  restarts=4  failed=1
  T-M1      retries=4  restarts=4  failed=1   (identical)

So T-M1's real cost is one spurious bus reset driven at a device the host is in the act of abandoning — the hazard the code comment describes, one cycle wide rather than permanent. And it is entirely invisible unless the bounds coincide, which is a property of the parameters, not of the stimulus.

T-M1b — innermost-first and exact-equality comparisons

The obvious follow-up: if >= is what rescued T-M1, what happens without it? Change the comparisons to == as well — which is how a counter check is often written, and which looks tidier.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
end else if (attempt_cnt == ATTEMPT_LIMIT) begin   // MUTANT T-M1b

Result, at the same collision point:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  golden    retries=4  restarts=3  failed=1  attempt_active=0
  T-M1b     retries=4  restarts=5  failed=0  attempt_active=1
                                   ^^^^^^^^  the attempt NEVER fails

The host waits forever. The attempt bound expired on a cycle where an inner action won, the count moved past the one value the comparison recognises, and the condition can never be true again. The supervisor restarts the device indefinitely and never reports failure — the denial of service the bound exists to prevent, now real.

T-M2 — parameters that do not nest

No logic change at all. Set the per-transfer bound larger than the step bound, which is a plausible mistake in a parameter file and produces no compile error.

Result. nesting_violation goes high, T5 fires on the first cycle, and T3 is disabled by its own disable iff — correctly, since the property is only meaningful when the parameters are sane. Without those two, the observable symptom is that no retry is ever issued: the step bound always fires first, so every transient problem escalates straight to a full reset and restart.

That is the failure mode worth remembering. The system still works. It recovers from everything it used to recover from. It is simply doing so with the most expensive mechanism available, having silently lost the cheapest one — and nothing anywhere reports an error.

7. What This Means for a Device Controller

Three obligations, and they are of different kinds.

Meet the minimums the device owns. The device's only number in §1 is the reset-detection threshold, and the obligation is a lower bound: do not believe a reset too early. A controller that reacts at the first sample will reset itself on ordinary traffic gaps.

Be ready within the host's patience. The reset-recovery and SetAddress-recovery intervals are the host's promises, and the device's obligation is to be genuinely ready when they expire. This is the obligation most likely to be met on a bench and missed in production, because the bench runs a warm, fast, uncontended device.

Leave margin for retries. §3's consequence: fitting inside the step bound is not enough when the step bound must also cover a retry.

8. Verification

This chapter's commit point is timing closed — the module's last, and the only one that is a property of the whole flow rather than an event within it.

Stimulus. Each interval exercised at its boundary as well as comfortably inside it; a device that responds at the last possible moment within the host's patience, and one that responds just after it; a reset shorter than the device's detection threshold, which must not be treated as a reset; an idle gap just below the threshold and one just above; and a transfer that times out and is retried, at each step of the flow.

The requirement that distinguishes this chapter's stimulus: the interesting values are at the edges. A test that exercises every interval at a comfortable midpoint verifies that the design works, and says nothing about the margin — which is the only thing that determines whether it works in the field.

And one requirement §6 discovered rather than predicted: vary the bounds so they collide. Both ordering mutants were invisible with the default parameters and appeared only once an outer bound was placed exactly on an instant when an inner one also expired. That coincidence is a property of the parameters, not of the stimulus — so no amount of interesting device behaviour will produce it, and a sweep of the bounds is the only thing that will.

Observation. Not merely whether each step completed, but how much of each interval it consumed. A step that completes using 95% of its bound has passed and is a defect report, because §3 requires room for a retry.

Reference model. Chapter 6.6 §4's is deliberately event-ordered with no notion of duration, and it should stay that way. Timing belongs in a separate layer of checks, because a model that mixes ordering and duration is wrong in ways that are hard to see — a divergence could be either an ordering bug or a timing bug, and the model can no longer say which.

Representative coverage — crosses:

  • each interval at its minimum × comfortably inside it × just beyond it
  • device response latency near the host's bound × well inside it
  • transfer timeout and retry × each step of the flow
  • step-bound exhaustion → reset and restart, from each step
  • reset durations just below × just above the device's detection threshold
  • idle gaps just below the detection threshold, which must not be seen as resets

Negative cases with defined outcomes: a bus condition shorter than the detection threshold must leave the device's state untouched; a device that misses the host's bound must cause a retry and then an escalation, not an indefinite wait; and a step that exhausts its bound must trigger a reset, not a continued attempt with stale state — Chapter 6.6 §6 measured what a restart carrying stale state costs.

9. Common Misconceptions

10. Reason It Through

A device controller passes every timing test on the bench. In the field it fails to enumerate roughly one time in twenty, always at the same step, and always recovering on the next attempt.

What does intermittency tell you? That the design is marginal, not wrong. A wrong design fails deterministically; a marginal one fails when conditions are slightly worse than the bench's — a longer cable, a busier host, a colder start.

What does always the same step tell you? That one step is the one closest to its bound. The others have margin; this one does not.

What does recovers on the next attempt tell you? That the host is escalating correctly, and that the recovery path works. This is the system behaving as §3 designed it: the step bound was exhausted, the host reset and restarted, and the retry succeeded. The failure is real and the recovery is not a bug — which is why this is reported as intermittent enumeration rather than as a dead device.

Why did the bench miss it? Because the bench measured pass or fail, not how much of the bound was consumed. A step finishing at 95% of its bound passes every test and is one bad cable away from failing — §8 asks for the consumption to be observed for exactly this reason.

What is the fix, and what is not? The fix is to find what makes that step slow and reduce it. What is not a fix is asking the host for a longer bound: the bounds are the host's, they vary between implementations, and a device that needs a generous one works only on generous hosts.

And the transferable lesson? Intermittent, same place, recovers is a margin signature. It points at a measurement — how close is this to its limit — rather than at a logic search, and that redirection is usually worth more than the eventual fix.

11. Understanding Check

12. Summary

Enumeration's intervals are derivable, not arbitrary. Attach debounce is 100 ms because connectors are mechanical. Reset assertion is at least 10 ms because it must be unmistakable to the least accurate device on the bus. The device's reset-detection threshold is at least 2.5 µs because idle must not be mistaken for reset. Reset recovery is 10 ms and SetAddress recovery is 2 ms, and the fivefold ratio is a readout of the two jobs' relative sizes — rebuilding all protocol state against committing one register.

Four of the five waits belong to the host or hub. That asymmetry is the host-centric model in the time domain: every interval buys device simplicity with host patience, which is the right trade when there is one host and many devices. It also generalises — in any protocol with one scheduling authority, the margins will be on the authority's side.

None of those numbers is a timeout. They are waits. Timeouts answer when do we give up, and they nest: transfer inside step inside attempt, each outer bound necessarily larger than what it contains, or the inner one can never fire. Exhausting each layer escalates differently — retry, then reset and restart, then declare failed — and that escalation is how a host distinguishes a slow device from a broken one. A host cannot simply wait longer, because patience spent on one device is denied to every other.

For a device controller the obligations are to meet the minimums it owns, to be genuinely ready when the host's patience expires, and to leave room for a retry — fitting inside the step bound is not enough when the step bound must also cover one.

Mutation testing added one lesson that was not predicted. An escalation ladder checked innermost-first is survivable on its own, because comparisons written with >= make every expiry sticky — an action that loses a cycle fires on the next one. Change those to == as well, and the attempt bound becomes permanently unreachable: the supervisor restarts a dead device forever and never declares failure. Two individually defensible choices, each survivable alone and catastrophic together — and both invisible until the bounds are made to actually coincide, which is a property of the parameters rather than of any stimulus.

And the reason timing defects are the hardest bugs here: they are the only conditional ones. Verification must attack the margin rather than the function, exercise the boundaries rather than the midpoints, and observe how much of each bound was consumed — because it passed says nothing about how close it came.

13. Where This Leaves You

Module 6 is complete. From a device being plugged in to a device being usable, every step has been derived rather than described: why the hub waits, why reset is an assertion rather than a question, why an address is captured before it is applied, why the first descriptor read is deliberately short, why described is not the same as usable, where the seams between those steps hide their defects, and what the whole thing costs in time.

The enumeration questions asked in interviews are now derivable rather than memorised — which matters, because the follow-up to what happens when you plug in a USB device is almost always why, and that is the question this module has been answering throughout.

What comes next fills in what this module deliberately treated as opaque. Module 7 opens the descriptors themselves — the contents Chapter 6.4 read without ever looking inside. Module 8 formalises the device states this module drove informally. Module 9 explains the endpoints Chapter 6.5 brought to life without defining. And Modules 11 through 13 open the transfers that every arrow in this module's diagrams stood for, down to the packets they are made of.

Browse the full path on the USB tutorials index.

Continue learning

Standards & specifications

Governing standard
USB-IF (Universal Serial Bus Specification)(opens USB Implementers Forum (USB-IF) in a new tab)

Defines the USB bus — its electrical signalling, connectors, packet and transaction model, device framework and the descriptors a device must expose — together with the device-class specifications layered on it. It does not define host-controller register interfaces (xHCI and EHCI are separate documents) nor any operating system's driver architecture.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the USB curriculum.