Skip to content
VLSI Mentor

USB · Module 6

End-to-End Enumeration Flow

The whole sequence from attach to usable device as one flow and one state machine — and the class of defect that lives only between steps, where every block verifies correctly and the composition still fails.

Five chapters, five commit points: attached, reset, addressed, described, configured. Each was studied alone, with its own RTL, its own properties, and its own characteristic failure.

This chapter puts them together, and it is worth doing for a reason beyond review.

Every step in this module has now been verified against its own rules. The bugs that remain are the ones that live between steps.

That is not a rhetorical flourish. Chapter 6.3 §7 already produced one: an address register whose defect was invisible in every property written about addressing, and whose damage appeared inside the state machine of Chapter 6.2. A module-level view is how that class of defect becomes visible at all.

1. The Whole Flow

Read this as one narrative before examining any part of it.

A device is plugged in. The hub notices an electrical change and, crucially, waitsChapter 6.1 showed why the wait is a correctness requirement rather than a convenience. Once the connection is stable the host enables the port and asserts a reset, which Chapter 6.2 showed is a statement rather than a question: the device ends in Default, reachable at the default address, with no address and no configuration.

The host then assigns an address. Chapter 6.3 showed that the device captures it, completes the transfer at the old address, and only afterwards begins answering at the new one — then the host allows a recovery interval. The device is now in Address: reachable, and useless.

Next the host asks the device to describe itself. Chapter 6.4 showed the first read is deliberately short, because the packet size the host needs is inside the description it is fetching, and that the same bootstrap recurs when the configuration subtree is read. The host now knows what the device is.

Finally the host selects a configuration. Chapter 6.5 showed this resolves the device's alternatives, brings up its endpoints, and commits a power budget. The device is in Configured, and usable.

A sequence diagram of the complete enumeration flow between a host, a hub and a device. The device attaches and the hub detects an electrical change, then waits for the connection to become stable before reporting it. The host enables the port and asserts a bus reset, after which the device is in the Default state and reachable at the default address. The host sends a SetAddress request; the device captures the address, completes the transfer at the old address, and then commits the new one, after which the host allows a recovery interval. The host reads a short prefix of the device description to learn the control endpoint packet size, then reads the full description. It reads a short prefix of a configuration to learn the subtree total length, then reads the whole configuration including its interfaces and endpoints. Finally the host selects a configuration and the device becomes usable.Attach to usable device — the full enumeration sequenceDeviceHubHostattach — electricalchange (6.1)wait for stability —debounceport has a deviceenable the portassert bus reset(6.2)now DEFAULT —address 0, no configSetAddress(N) — toaddress 0 (6.3)completes at address0, THEN commitsallow recoveryinterval (6.7)describe yourself —short prefix (6.4)prefix — revealspacket sizedescribe yourself —full lengthdescribeconfiguration —short prefixprefix — revealstotal lengthdescribeconfiguration — fulllengthconfiguration +interfaces +endpointsSetConfiguration(non-zero) (6.5)now CONFIGURED —endpoints live
Figure 1 — the whole sequence. Every arrow is a complete transfer; the two short reads exist only to learn the length of the reads that follow them.

2. The Flow as One State Machine

A state machine of the device's protocol states during enumeration. From an attached but un-reset state the device enters Default on a bus reset. From Default an address commit moves it to Address. From Address a non-zero configuration selection moves it to Configured. From Configured a selection of the reserved value zero returns it to Address. A bus reset returns the device to Default from the Address state and from the Configured state alike, discarding the address and any configuration.AttachedDefaultAddressConfiguredbus reset (6.2)bus reset (6.2)address commit (6.3)address commit (6.3)SetConfiguration non-zero (6.5)SetConfigurationnon-zero (6.5)SetConfiguration 0SetConfiguration 0bus resetbus resetbus resetbus reset
Figure 2 — the device's view. Note that reset reaches Default from everywhere, which is what makes it a recovery mechanism and not merely a first step.

One observation carries most of the weight of this diagram. Reset reaches Default from every state. The device's protocol state can always be forced to a known value by an action the host can take unilaterally, at any moment, without the device's cooperation and without knowing what state it is in.

That is why enumeration is restartable. Any step can fail, at any point, and the host's recovery is always the same: assert reset and begin again. The flow needs no error-recovery paths of its own because it has exactly one, and it is the same edge that starts the sequence.

And it is why the reset scope of Chapter 6.2 matters so much. If a reset failed to clear some piece of protocol state, that state would survive a recovery — and the restart, which is supposed to be a clean slate, would instead be the mechanism by which stale state propagates into the next attempt.

3. Where the Seams Are

Now the part a module-level view exists for.

Each chapter verified one step against its own rules. That is necessary and it is not sufficient, because correctness of the parts does not compose into correctness of the whole when the parts share state or ordering. Three seams in this flow carry that risk.

Seam 1 — the address commit and the device state machine. Chapter 6.3 produces a commit pulse; Chapter 6.2 consumes it as the device has been addressed. Neither owns the pulse's meaning jointly. 6.3 §7 measured the consequence: a register that emits a spurious commit pulse passes every property written about addresses, and knocks a Configured device back to Address — a defect in one module whose damage is entirely in another.

Seam 2 — a reset arriving mid-sequence. Every chapter handles a reset correctly in isolation. The composition question is different: when a reset arrives during the descriptor walk, does everything learned and committed go away together? The address register must clear, the walk must abandon its learned lengths, the configuration must clear, and the state must return to Default — and they must do so consistently, because a device that clears three of the four is in a state the protocol has no name for.

Seam 3 — ordering between steps. Nothing in the individual chapters prevents a configuration commit from being accepted by a device that has not yet been addressed, because each chapter verified its own transition. Order is a property of the composition, and so it must be checked there.

4. The Enumeration Reference Model

This is a verification model, not a design. It exists to be compared against, and the difference matters: it is written for clarity and checkability, with no concern for synthesis.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_enum_model
//
// Classification: VERIFICATION MODEL. NOT synthesizable, NOT a design, and
// NOT a USB device controller. It is a golden reference for the ENUMERATION
// FLOW: what protocol state the device should be in after each event, and
// which events are legal in each state.
//
// WHY IT EXISTS. Chapters 6.1-6.5 each verified one step against its own
// rules. This model exists to check the SEAMS of section 3 -- ordering
// between steps, and the consistency of everything a reset must clear.
//
// WHAT IT MODELS. The protocol state, the address, the configuration, and
// how far the descriptor walk has progressed -- the four pieces of state
// the flow as a whole is about.
//
// WHAT IT DOES NOT MODEL. Everything else, deliberately: transfer
// mechanics, packets, endpoints, timing (Chapter 6.7 owns durations; this
// model is event-ordered and has no notion of a nanosecond), descriptor
// contents, hubs, power, or any speed-dependent behaviour.
//
// HOW TO USE IT. Drive it with the same decoded events as the DUT and
// compare after every event. Divergence is a bug in one of them; which one
// is a question the model cannot answer and a human must.
// ─────────────────────────────────────────────────────────────────────────
package usb_enum_model_pkg;

  typedef enum {
    ST_ATTACHED,    // present, no reset seen
    ST_DEFAULT,     // reset complete -- answers at the default address
    ST_ADDRESS,     // has an address, not usable
    ST_CONFIGURED   // a configuration is selected, endpoints live
  } model_state_e;

  typedef enum {
    EV_ATTACH,
    EV_BUS_RESET,
    EV_ADDR_COMMIT,     // carries a value
    EV_DESC_PREFIX_OK,  // a short read completed
    EV_DESC_FULL_OK,    // a full read completed
    EV_CFG_COMMIT,      // carries a value; zero is an un-select
    EV_DETACH
  } model_event_e;

endpackage

// The import sits OUTSIDE the class: a package import is not a legal class
// item, only a compilation-unit, module or package item.
import usb_enum_model_pkg::*;

class usb_enum_model;

  model_state_e state;
  int unsigned  addr;
  int unsigned  config_val;
  int unsigned  desc_reads_done;   // how far the walk has progressed
  int unsigned  illegal_events;    // events the device should have refused

  function new();
    reset_to_detached();
  endfunction

  // Everything the model knows, cleared. Distinct from a BUS reset, which
  // is a protocol event and leaves the device attached.
  function void reset_to_detached();
    state           = ST_ATTACHED;
    addr            = 0;
    config_val      = 0;
    desc_reads_done = 0;
    illegal_events  = 0;
  endfunction

  // ── The seam-2 rule, in one place ───────────────────────────────────────
  // A bus reset clears EVERY piece of protocol state together. Keeping this
  // in a single function is deliberate: the defect it guards against is a
  // design that clears some of these and not others, and a model that
  // scattered the clearing across the event handler could grow exactly the
  // same defect and then agree with the DUT about it.
  function void apply_bus_reset();
    state           = ST_DEFAULT;
    addr            = 0;
    config_val      = 0;
    desc_reads_done = 0;
  endfunction

  function void event_in(model_event_e ev, int unsigned val = 0);
    case (ev)

      EV_ATTACH: begin
        reset_to_detached();
      end

      EV_DETACH: begin
        reset_to_detached();
        state = ST_ATTACHED;
      end

      // Legal from ANY state -- this is section 2's central observation, and
      // the reason enumeration is restartable.
      EV_BUS_RESET: begin
        apply_bus_reset();
      end

      // ── Seam 3: ordering ──────────────────────────────────────────────
      // An address may only be committed by a device that has been reset.
      EV_ADDR_COMMIT: begin
        if (state != ST_DEFAULT && state != ST_ADDRESS
            && state != ST_CONFIGURED) begin
          illegal_events++;
        end else if (val == 0) begin
          // Committing the default address is not an assignment.
          illegal_events++;
        end else begin
          addr  = val;
          state = ST_ADDRESS;
          // Re-addressing a configured device is not something a host does,
          // but if it happens the configuration cannot survive it: the
          // device has been re-identified.
          config_val = 0;
        end
      end

      EV_DESC_PREFIX_OK: begin
        if (state != ST_ADDRESS && state != ST_CONFIGURED) illegal_events++;
        else desc_reads_done++;
      end

      EV_DESC_FULL_OK: begin
        // A full read is only meaningful after the prefix read that gave it
        // a length -- Chapter 6.4's gate, checked here at flow level.
        if (state != ST_ADDRESS && state != ST_CONFIGURED) illegal_events++;
        else if (desc_reads_done == 0) illegal_events++;
        else desc_reads_done++;
      end

      // ── Seam 3 again, and Chapter 6.5's value rule ────────────────────
      EV_CFG_COMMIT: begin
        if (state != ST_ADDRESS && state != ST_CONFIGURED) begin
          // A device that has not been addressed cannot be configured.
          illegal_events++;
        end else begin
          config_val = val;
          state      = (val == 0) ? ST_ADDRESS : ST_CONFIGURED;
        end
      end

      default: illegal_events++;
    endcase
  endfunction

  // ── Checks the model asserts about ITSELF ───────────────────────────────
  // A reference model with an internal contradiction is worse than no model,
  // because the comparison then passes on a state neither side should be in.
  function bit self_consistent();
    if (state == ST_CONFIGURED && config_val == 0)       return 0;
    if (state == ST_CONFIGURED && addr == 0)             return 0;
    if (state == ST_ADDRESS    && addr == 0)             return 0;
    if (state == ST_DEFAULT    && (addr != 0 || config_val != 0)) return 0;
    return 1;
  endfunction

endclass

Classification. Verification model. Not synthesizable and not a design.

What it models. The four pieces of state the flow is about, the legality of each event in each state, and the single rule that a bus reset clears everything together.

Why it exists. To check §3's seams, which no chapter's own properties cover.

What it deliberately omits. Transfer mechanics, packets, endpoints, timing, descriptor contents, hubs, power and speed. It is event-ordered and has no notion of durationChapter 6.7 owns that, and mixing the two would make this model wrong in a way that is hard to see.

How to use it. Drive it with the same decoded events as the DUT, compare after each, and treat divergence as a bug in one of them without assuming which.

Two design decisions inside it are worth naming, because both are about the model's own trustworthiness:

  • apply_bus_reset is one function. The defect being hunted is a design that clears some protocol state and not the rest. A model that scattered the clearing across its event handler could acquire that same defect and would then agree with the broken DUT, which is the worst possible outcome for a reference model.
  • self_consistent exists at all. A model that has drifted into a contradictory state will happily compare against a DUT in the same contradictory state. Checking the model against its own invariants is what stops a comparison from passing on a state neither side should occupy.

5. Flow-Level Assertions

These are properties of the composition, not of any one chapter's block. They are the ones nobody's block-level property set contains.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Flow-level assertions. Written against the DEVICE STATE and the decoded
// commit events, i.e. across the seams of section 3.
//
// SCOPE NOTE. These bind to a COMPOSED environment, not to any one of this
// module's blocks -- that is the point of them. Two of the signals they use
// exist only at that level: `walk_in_progress`, from Chapter 6.4's
// sequencer, and `request_outstanding`, which the environment tracks the way
// Chapter 6.3's A6 does, deliberately NOT reusing any design's own pending
// flag. A property that borrows the flag it is meant to police cannot
// police it.
// ─────────────────────────────────────────────────────────────────────────

// F1 -- ORDERING. A device cannot be configured before it is addressed.
// No chapter's own properties cover this: 6.5 verified its transition, and
// "was there ever an address" is not a fact 6.5 owns.
property p_no_config_before_address;
  @(posedge clk) disable iff (!rst_n)
    cfg_committed |-> (dev_state == CS_ADDRESS || dev_state == CS_CONFIGURED);
endproperty
assert property (p_no_config_before_address);

// F2 -- SEAM 2. A bus reset clears every piece of protocol state TOGETHER.
// The bug this exists for is a design that clears three of four, leaving
// the device in a combination the protocol has no name for.
property p_reset_clears_everything_together;
  @(posedge clk) disable iff (!rst_n)
    bus_reset |=> (dev_state       == CS_DEFAULT
                && active_addr     == 7'd0
                && active_config   == 8'd0
                && !endpoints_live
                && !walk_in_progress);
endproperty
assert property (p_reset_clears_everything_together);

// F3 -- SEAM 1. Every commit event must be one the flow actually asked for.
// Generalises Chapter 6.3's A6 to the flow: a commit pulse of any kind with
// no outstanding request is a spurious event, and section 3 showed that its
// damage lands in a different block from the one that emitted it.
property p_commits_have_causes;
  @(posedge clk) disable iff (!rst_n)
    (addr_committed || cfg_committed) |-> request_outstanding;
endproperty
assert property (p_commits_have_causes);

// F4 -- the device is never usable without being identifiable. Endpoints
// live while the device has no address is a device sending traffic nobody
// can attribute.
property p_usable_implies_addressed;
  @(posedge clk) disable iff (!rst_n)
    endpoints_live |-> (active_addr != 7'd0);
endproperty
assert property (p_usable_implies_addressed);

// F5 -- RESTARTABILITY, the property section 2 is about. From ANY state, a
// bus reset reaches Default. Written with no antecedent state constraint
// deliberately: the moment a property about reset needs to know where the
// device was, reset has stopped being unconditional.
property p_reset_always_reaches_default;
  @(posedge clk) disable iff (!rst_n)
    bus_reset |=> (dev_state == CS_DEFAULT);
endproperty
assert property (p_reset_always_reaches_default);

F1 and F3 are the two that exist only here. Every other property in this module belongs to some chapter; these two belong to the flow, and nothing smaller than the flow can state them.

F5 deserves its comment. The temptation when writing a reset property is to enumerate the states it applies from. Doing so quietly converts an unconditional rule into a conditional one, and the state that gets left out of the enumeration is the one where the bug is.

6. What the Flow View Catches

Two defects, both of which pass every block-level property in this module. Both were run; the outputs below are measured.

A spurious commit pulse. Chapter 6.3 §7 measured this one: an address register that leaves its pending flag set re-commits on the next unrelated completion. Its own five properties all pass — there is no wrong address anywhere — and wiring it to Chapter 6.2's state machine showed a Configured device falling back to Address, silently losing its configuration. F3 catches it at flow level for the same reason 6.3's A6 catches it at block level: a commit is an event that requires a cause.

A partial reset. Suppose a controller's reset handling clears the address and the configuration but leaves the descriptor walk holding what it learned. This one was run against §4's model with exactly that mutation, and the result is worth reading in full.

Every per-block reset check passes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS  6.2's check: state returned to DEFAULT
PASS  6.3's check: address cleared
PASS  6.5's check: configuration cleared
PER-BLOCK RESULT: ALL PASS (0 errors)

Three chapters, three correct properties, three passes. Each checks the state it owns, and each is satisfied. Chapter 6.4's W6 would catch it — if the walk sequencer is being verified at the same time, with reset stimulus, which at integration it frequently is not.

The flow-level check fails immediately:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
F2-FAIL: reset did not clear everything together (walk progress = 2)

And the measured consequence is the part to carry away. On the second enumeration, the model was driven with a full descriptor read that had no preceding prefix read — an illegal sequence, and one the correct model records as illegal. The mutant accepted it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
GOLDEN  second enumeration: illegal_events=1  desc_reads_done=0
Q1      second enumeration: illegal_events=0  desc_reads_done=3
        >>> the un-gated full read was ACCEPTED -- stale walk progress authorised it

Read that last line carefully. The stale value did not merely linger; it authorised an operation that should have been refused. Chapter 6.4's gate was structurally intact and gating on a fact left over from the previous enumeration.

F2 catches it because it checks the clearing as one atomic requirement rather than as four separate ones. That is the whole difference between four properties that each pass and one property that fails: the conjunction is the requirement, and splitting it across owners loses it.

7. Verification

This chapter's commit point is enumeration complete — the whole flow, and the only one no single block owns.

Stimulus. A clean enumeration end to end; then the same flow with a bus reset injected at each step in turn, including between a request and its commit; a failed transfer at each step; a device that is detached mid-enumeration; an un-select followed by a re-select; and a second full enumeration after a completed one, which is where stale state from the first attempt becomes visible.

The requirement that makes this chapter's stimulus different from every earlier one: the interesting cases are crossings, not points. A reset during the descriptor walk is not covered by testing reset and testing the walk. §6's second defect exists precisely in that crossing.

Observation. The four model variables together — state, address, configuration, walk progress — compared after every event. Comparing them individually is what lets a partial reset pass.

Reference model. §4's, driven from the same decoded events. Check self_consistent() after each comparison as well, so that a model which has drifted cannot silently endorse a DUT that has drifted with it.

Representative coverage — crosses:

  • bus reset × every step of the flow, including mid-transfer
  • transfer failure × every step
  • device state × each commit event, so that illegal orderings are exercised rather than assumed absent
  • un-select × re-select × reset, in each order
  • second enumeration after a complete first one × after an aborted first one

Negative cases with defined outcomes: a configuration commit on an unaddressed device must be refused, not accepted; an address commit of the default address is not an assignment and must not move the device to Address; and a second enumeration must produce a device indistinguishable from a first-time one, with nothing carried over.

8. Common Misconceptions

9. Reason It Through

A device enumerates perfectly. It is unplugged and plugged back in. The second enumeration fails — the host reports the device as unresponsive partway through the descriptor walk. Unplugging it and waiting several seconds makes the next attempt succeed.

What does the timing dependence tell you? That something persists across the re-attach and decays or is cleared with time. That points at state which survived when it should not have, rather than at a logic error in any single step — a step that was simply wrong would fail on the first enumeration too.

Why does the first enumeration always work? Because there is no stale state to survive into it. A defect of this shape is invisible by construction on a first attempt, which is why §7 requires a second full enumeration as explicit stimulus.

Which seam is this? §3's second: a reset that does not clear everything together. Something from the first enumeration — a learned length, a pending value, a walk stage — was not cleared, and the second attempt begins with a device that believes it already knows something.

Why does the failure land in the descriptor walk specifically? Because the walk is the first step whose behaviour depends on remembered values rather than on the request in front of it. Reset and addressing are self-contained; the walk acts on lengths learned earlier. A stale length is used the moment the walk reaches a full read.

What would have caught it before silicon? F2, as one property. Note what would not have: each block's own reset property, all of which pass — §6 measured exactly that, three per-block reset checks passing against a design that leaves the walk's state behind.

And §6 measured the mechanism too: the stale value does not merely linger, it authorises a read that should have been refused, because the gate that was supposed to permit it was satisfied by a fact left over from the previous enumeration.

And the debugging lesson? Works the first time, fails the second, recovers after a delay is a signature worth memorising. It says state, not logic — and it points at whatever was supposed to clear that state rather than at whatever failed.

10. Understanding Check

11. Summary

Enumeration is five commit points — attached, reset, addressed, described, configured — and one unconditional edge. Reset reaches Default from every state, which is what makes the flow restartable and why no individual step needs an error-recovery path: there is exactly one, and it is the same edge that starts the sequence.

Verifying each step against its own rules is necessary and not sufficient. Requirements that span blocks — cleared together, only after, never without — are lost the moment they are distributed into per-block properties that each pass. Two measured defects in this module have exactly that shape: a spurious commit pulse, whose damage lands in a different block from the defect, and a partial reset, where four correct per-block reset properties coexist with a device that clears three things out of four.

The flow-level properties are the ones nobody's block owns: a device cannot be configured before it is addressed; a reset clears everything together, as one atomic requirement; every commit event corresponds to something that was requested; a device is never usable without being identifiable; and a reset reaches Default from anywhere, written with no antecedent state constraint, because enumerating states converts an unconditional rule into a conditional one.

The reference model is a verification model — event-ordered, with no notion of duration — and two things about it are deliberate: the reset clearing is one function, so the model cannot grow the defect it is hunting and then agree with the DUT; and it checks its own invariants, so a drifted model cannot endorse a drifted DUT.

And the stimulus that separates this chapter from every earlier one: the interesting cases are crossings. A reset during the descriptor walk is not covered by testing reset and testing the walk separately, and a second full enumeration is the only stimulus under which stale state can be observed at all.

12. What Comes Next

The flow is complete and its structure is understood. One dimension has been deliberately held back throughout: time.

Every chapter so far has been about order — what must happen before what. Nothing has said how long any of it takes, how long the host waits, or what happens when a step takes longer than it should. Figure 1's vertical axis was explicitly not time.

Chapter 6.7 supplies those numbers, and it is more interesting than a table of durations. Each one exists for a reason that can be derived from something this module has already established, and the timeouts form layers — the host's patience at one level is bounded by its patience at the level above. A device that is merely slow and a device that is broken must eventually become the same thing to a host, and where that line falls is a design decision with consequences for every controller that has to meet it.

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.