Skip to content
VLSI Mentor

USB · Module 6

Descriptor Discovery

Why the host's first descriptor read is deliberately incomplete, why that partial read is a recurring pattern rather than a workaround, why the walk order is forced, and the gated sequencer RTL that intuition gets wrong.

Chapter 6.3 left the host able to reach one specific device at one specific address. It still has no idea what that device is.

This chapter is how that changes. The host asks the device to describe itself and reads the answer — which sounds like the least interesting step in enumeration and is not, because of one property that shapes everything about it:

The information the host needs in order to read efficiently is itself inside the thing it is trying to read.

That circularity — the third this module has hit, after reset and addressing — is why the host's very first read is deliberately incomplete, and why that incompleteness is a recurring pattern rather than a workaround.

Scope note. This chapter is about the walk: what the host asks for, in what order, and why. Module 7 owns descriptor contents and layout — every field, every byte offset, every type code. Nothing here depends on knowing those; the reasoning is structural.

1. The Bootstrap Problem

Start from what the host knows after Chapter 6.3. It knows a device exists at address N, operating at a known speed, in the Address state. Now it wants the device's description.

To request it, the host must construct a transfer. And to conduct that transfer efficiently it needs to know how much data the device can carry in one packet on its control endpoint — a property that varies by device and by speed, and which is recorded inside the device's own description.

So the host needs the descriptor to read the descriptor efficiently. Stated that baldly it looks like a design flaw. It is not, and the resolution is worth deriving.

What does the host know without asking? One thing: a floor. Whatever the device's control-endpoint packet size turns out to be, it cannot be smaller than the smallest size the specification permits — and Chapter 5.1 established that floor as 8 bytes, the value low-speed devices are limited to. Every device at every speed can carry at least that much.

That floor is the whole solution. The host requests the description, and reads only as much of it as the floor guarantees will arrive intact. The specification places the packet-size value early enough to be inside that guaranteed prefix — so a first read bounded by the floor always reveals it.

Then the host knows the real size, and can read the rest properly.

2. Why the Walk Has an Order

The host does not fetch descriptors in an arbitrary sequence, and the reason is not convention.

Descriptions are nested. A device offers one or more configurations. A configuration contains interfaces. An interface contains endpoints. That is a containment hierarchy, and it has a consequence: you cannot ask about a thing whose existence you have not yet learned.

Reason it through from the host's position:

  • Before reading anything, the host does not know how many configurations the device offers. It therefore cannot ask for configuration number two — it does not know whether there is one.
  • The count of configurations is in the device description. So that must be read first.
  • Having read it, the host can ask for a configuration by number, because now it knows the range is valid.

The order is forced by what each step reveals, exactly as the whole module has been: at every point the host may only act on what it already knows. Chapter 6.1's attach detection, 6.2's reset, 6.3's address assignment and now the descriptor walk are four instances of one principle.

3. The Same Problem, Again

Now apply §2's result to §1's problem.

The host is about to request a configuration, and the device will return the whole subtree. How large is it? It depends on how many interfaces the configuration has and how many endpoints each of those has — none of which the host knows, because that information is in the block it is asking for.

That is §1's bootstrap, exactly. The size of the thing is inside the thing.

And the resolution is the same shape: the host first reads a short prefix, long enough to contain a total-length value the specification places near the front, learns the real size from it, and then re-reads the block with the correct length.

4. The Walk

A sequence diagram of descriptor discovery. The host first requests the device description but asks only for a short prefix bounded by the guaranteed minimum packet size. The device returns that prefix, from which the host learns the control endpoint's real packet size. The host then requests the full device description using the correct size. Next the host requests a configuration, again asking only for a short prefix, from which it learns the total length of the configuration subtree. The host then requests the configuration again with that total length, and the device returns the configuration together with its interfaces and endpoints as one contiguous block. The host now has a complete picture of the device.Descriptor discovery — the order and the partial readsHostDevice at address Ndescribe yourself —short prefix onlyprefix — revealscontrol packet sizedescribe yourself —full, correct lengthfull devicedescriptiondescribeconfiguration 1 —short prefix onlyprefix — revealstotal subtree lengthdescribeconfiguration 1 —full total lengthconfiguration +interfaces +endpoints, one block
Figure 1 — the two bootstrap reads are the first and third exchanges. Each short read exists solely to learn the length of the read that follows it.

Descriptor-walk sequencer — stage and requested length

6 cycles
A sequencer view of the descriptor walk. The stage advances through device prefix, device full, configuration prefix, configuration full, and done. The requested length is the safe floor of eight bytes during both prefix stages, and a learned value during both full stages. Two learned-value-valid flags become asserted in turn as each prefix read completes, and each full read is gated on the corresponding flag. The figure shows sequencer state, not bus signalling, and depicts no real durations.safe floor — no knowledge requiredsafe floor — no knowledgerequiredgated on mps_validgated on mps_validgated on len_validgated on len_validstageIDLEDEVpreDEVfullCFGpreCFGfullDONEreq_len--8learned8learned--mps_validlen_validxfer_donet0t1t2t3t4t5
Figure 2 — the sequencer's own view. Each requested length is either the safe floor or a value learned from the preceding read; it is never a guess.

5. The Walk, as RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_desc_walk
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models the
// ORDERING and LENGTH-GATING of the descriptor walk, and nothing else.
//
// WHAT IT MODELS. Sections 1-3: each full read is gated on a value learned
// from a preceding prefix read, the prefix reads use a floor that requires
// no prior knowledge, and the stages advance in an order forced by what
// each one reveals.
//
// WHAT IT DOES NOT MODEL. Descriptor contents, fields or offsets (Module 7
// owns them -- the learned values arrive here as opaque numbers), control
// transfer mechanics (Module 13 -- xfer_done is an already-qualified
// event), packets (Modules 11-12), endpoints (Module 9), or the host side
// of any of this. A REAL host may also retry, abort the walk, or read
// additional descriptor types; this models the mandatory spine only.
// ─────────────────────────────────────────────────────────────────────────
package usb_desc_pkg;
  typedef enum logic [2:0] {
    DW_IDLE     = 3'd0,
    DW_DEV_PRE  = 3'd1,   // short read of the device description
    DW_DEV_FULL = 3'd2,   // full read, length known from DW_DEV_PRE
    DW_CFG_PRE  = 3'd3,   // short read of a configuration
    DW_CFG_FULL = 3'd4,   // full read, length known from DW_CFG_PRE
    DW_DONE     = 3'd5
  } dw_stage_e;

  // The floor of section 1: the smallest control-endpoint packet size any
  // device at any speed may have (Chapter 5.1). Safe without knowledge.
  localparam logic [15:0] DESC_SAFE_PREFIX = 16'd8;
endpackage

module usb_desc_walk
  import usb_desc_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  // Decoded protocol bus reset -- a LEVEL while asserted (Chapter 6.2).
  // A reset abandons the walk: everything learned is about a device that
  // has just been returned to Default, so none of it is still true.
  input  logic        bus_reset,

  input  logic        start,          // begin the walk (device is addressed)

  // Transfer results. Exactly one may pulse for a given request.
  input  logic        xfer_done,
  input  logic        xfer_failed,

  // The value the just-completed PREFIX read revealed. Opaque here --
  // extracting it from descriptor bytes belongs to Module 7.
  input  logic [15:0] learned_len,

  // Request interface to whatever conducts control transfers.
  output logic        req_valid,
  output logic [15:0] req_len,

  output dw_stage_e   stage,
  output logic        mps_valid,      // control packet size is known
  output logic        len_valid,      // configuration total length is known
  output logic [15:0] ep0_mps,
  output logic [15:0] cfg_total_len,
  output logic        walk_done
);

  assign walk_done = (stage == DW_DONE);
  assign req_valid = (stage != DW_IDLE) && (stage != DW_DONE);

  // The requested length is NEVER a guess. It is either the floor, which
  // needs no knowledge, or a value a preceding read supplied.
  always_comb begin
    unique case (stage)
      DW_DEV_PRE:  req_len = DESC_SAFE_PREFIX;
      DW_DEV_FULL: req_len = ep0_mps;
      DW_CFG_PRE:  req_len = DESC_SAFE_PREFIX;
      DW_CFG_FULL: req_len = cfg_total_len;
      default:     req_len = 16'd0;
    endcase
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      stage         <= DW_IDLE;
      mps_valid     <= 1'b0;
      len_valid     <= 1'b0;
      ep0_mps       <= 16'd0;
      cfg_total_len <= 16'd0;
    end else if (bus_reset) begin
      // Abandon. Everything learned describes a device that no longer has
      // the address it was learned at, so retaining any of it is a bug.
      stage         <= DW_IDLE;
      mps_valid     <= 1'b0;
      len_valid     <= 1'b0;
      ep0_mps       <= 16'd0;
      cfg_total_len <= 16'd0;
    end else begin
      unique case (stage)
        DW_IDLE:
          if (start) stage <= DW_DEV_PRE;

        DW_DEV_PRE:
          // A prefix read both advances the stage and supplies the value
          // the NEXT stage needs. Those happen together, deliberately:
          // the gate and the thing it gates must not drift apart.
          if (xfer_done) begin
            ep0_mps   <= learned_len;
            mps_valid <= 1'b1;
            stage     <= DW_DEV_FULL;
          end
          // A failure leaves the stage where it is, to be retried. It must
          // NOT advance -- the next stage's gate would then be unsatisfied.

        DW_DEV_FULL:
          if (xfer_done) stage <= DW_CFG_PRE;

        DW_CFG_PRE:
          if (xfer_done) begin
            cfg_total_len <= learned_len;
            len_valid     <= 1'b1;
            stage         <= DW_CFG_FULL;
          end

        DW_CFG_FULL:
          if (xfer_done) stage <= DW_DONE;

        DW_DONE: ;   // terminal until a reset or a new start

        default: stage <= DW_IDLE;
      endcase

      if (stage == DW_DONE && start) stage <= DW_DEV_PRE;  // re-walk
    end
  end

endmodule

What it models. The ordering and the length gating — the two structural facts of §§1–3.

Why this hardware exists. Because read a little, learn the size, read it properly is a sequence with dependencies, and a sequence with dependencies is a state machine. The dependencies are not stylistic: a full read issued before its prefix read has completed has no correct length to use.

Inputs. A clock and local reset; a decoded bus reset; a start; qualified completion and failure events; and the opaque value a prefix read revealed.

State retained. The stage, the two learned values, and a validity flag for each.

Outputs. A request with its length, the stage, the learned values with their flags, and a done indication.

Reset behaviour. Both resets abandon the walk completely and clear every learned value. The comment states why: after a bus reset the device is back in Default at the default address, so nothing learned about it at address N is still true.

Hardware implied. A six-state sequencer, two 16-bit registers, two flags, and a small output multiplexer.

Assumptions. That xfer_done and xfer_failed are qualified single-cycle pulses in this clock domain and are mutually exclusive per request; that learned_len is valid in the cycle xfer_done pulses; and that whatever conducts transfers honours req_len.

Deliberately omits. Descriptor contents entirely, control-transfer mechanics, packets, endpoints, retry policy, and the optional parts of a real host's walk.

What DV should verify. That a full read is never requested before its prefix read has completed; that each full read's length is the learned value rather than a constant; that a failure does not advance the stage; that a bus reset clears everything learned; and that the stages never occur out of order.

6. The Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Assertions for usb_desc_walk.
//
// Classification: TEACHING ASSERTIONS about this model's ordering and
// gating rules. They are not a USB compliance suite.
// ─────────────────────────────────────────────────────────────────────────

// W1 -- THE CENTRAL ONE. A full read may only be requested once the value
// that determines its length has actually been learned. This is sections
// 1-3 stated as an invariant, and section 7 shows what it catches.
property p_full_reads_are_gated;
  @(posedge clk) disable iff (!rst_n)
    ((stage == DW_DEV_FULL) |-> mps_valid) and
    ((stage == DW_CFG_FULL) |-> len_valid);
endproperty
assert property (p_full_reads_are_gated);

// W2 -- a prefix read uses the FLOOR, never a learned or guessed value.
// A design that "optimises" a prefix read by using a remembered size from
// a previous device is unsafe the first time a smaller device appears.
property p_prefix_uses_floor;
  @(posedge clk) disable iff (!rst_n)
    ((stage == DW_DEV_PRE) || (stage == DW_CFG_PRE)) |-> (req_len == DESC_SAFE_PREFIX);
endproperty
assert property (p_prefix_uses_floor);

// W3 -- a full read's length is the value its prefix read supplied.
property p_full_uses_learned;
  @(posedge clk) disable iff (!rst_n)
    ((stage == DW_DEV_FULL) |-> (req_len == ep0_mps)) and
    ((stage == DW_CFG_FULL) |-> (req_len == cfg_total_len));
endproperty
assert property (p_full_uses_learned);

// W4 -- a failed transfer must not advance the stage. Advancing on failure
// would leave the next stage requesting a length that was never learned.
property p_failure_does_not_advance;
  @(posedge clk) disable iff (!rst_n)
    (xfer_failed && !xfer_done && !bus_reset) |=> $stable(stage);
endproperty
assert property (p_failure_does_not_advance);

// W5 -- ORDERING. The stage may only move along the defined path. Written
// as an explicit legal-transition list rather than as "it advances by one",
// because the numeric encoding is an implementation detail and a property
// that depends on it stops meaning anything if the encoding changes.
property p_legal_transitions;
  @(posedge clk) disable iff (!rst_n)
    !$stable(stage) |-> (
         ($past(stage) == DW_IDLE     && stage == DW_DEV_PRE)
      || ($past(stage) == DW_DEV_PRE  && stage == DW_DEV_FULL)
      || ($past(stage) == DW_DEV_FULL && stage == DW_CFG_PRE)
      || ($past(stage) == DW_CFG_PRE  && stage == DW_CFG_FULL)
      || ($past(stage) == DW_CFG_FULL && stage == DW_DONE)
      || ($past(stage) == DW_DONE     && stage == DW_DEV_PRE)
      || ($past(bus_reset)            && stage == DW_IDLE)
    );
endproperty
assert property (p_legal_transitions);

// W6 -- a bus reset abandons everything learned. Retaining a value across
// a reset means describing a device using facts gathered before it was
// returned to Default.
property p_reset_abandons;
  @(posedge clk) disable iff (!rst_n)
    bus_reset |=> (stage == DW_IDLE && !mps_valid && !len_valid);
endproperty
assert property (p_reset_abandons);

All six were run against the correct design and against both mutants of §7; the results there are measured rather than predicted.

W1 and W5 are the pair worth internalising. W1 says never act on what you have not learned; W5 says learn things in the order that makes each step possible. Between them they are this chapter's entire thesis, expressed as something a simulator can check on every cycle.

W2 deserves a note, because the design it forbids is a tempting one. A host that has enumerated many devices may be holding a perfectly good packet size from the last one, and using it would save a transfer. It is unsafe: the next device may be slower and smaller, and the read would then exceed what it can deliver. The floor must be used because it is the only value valid without knowledge of this device.

7. Mutation Test

Two mutants, both plausible, both about order — which is what this chapter is about.

N1 — skip the prefix read and guess

The most common way to write this from intuition: the walk goes straight to the full read, using a packet size that is right for most modern devices.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
DW_IDLE:
  if (start) stage <= DW_DEV_FULL;   // MUTANT N1: skip the prefix read

Result. W1 fires immediately — the stage is DW_DEV_FULL with mps_valid low — and keeps firing for as long as the design sits in that stage. W5 fires alongside it, since IDLE → DEV_FULL is not a legal transition.

And the part that matters. A functional check asking does the walk complete, and does it end up with the configuration's total length? passes, measured. The walk reaches DW_DONE and the total length is correct, because the prefix read this mutant skipped only ever supplied the device packet size — the configuration path is untouched. The end state is entirely right.

That is the shape of the real bug too: a host that guesses the packet size works with every device whose size matches the guess, and fails only when a device with a smaller one is attached. It is why the assertion is written about the gate rather than the outcome.

N2 — advance the stage on any transfer result

Treating failure as completion, which is easy to do when a single xfer_result signal is destructured carelessly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
DW_DEV_PRE:
  if (xfer_done || xfer_failed) begin   // MUTANT N2: failure advances too
    ep0_mps   <= learned_len;
    mps_valid <= 1'b1;
    stage     <= DW_DEV_FULL;
  end

Result. W4 fires on the first failed transfer, and the walk then derails — every subsequent stage check fails, because the sequencer is one stage ahead of the data it has.

Two measurements are worth stating precisely.

First, W1 never fires on this mutant. Only W4 does. That is not an oversight in W1; it is the defect's actual shape. The mutant latches learned_len from a transfer that delivered nothing and raises mps_valid over it, so by the time the full read is issued the gate is genuinely set — W1 asks whether the flag is high, and it is. The design looks fully gated and is gating on garbage.

Second, with a stimulus containing no failing transfers, this mutant passes everything with zero errors. The same limitation Chapter 6.3 §7 measured, from a different direction: the property is sound, and a run that never fails a transfer cannot exercise it. §8 turns that into a stated requirement.

8. Verification

This chapter's commit point is device described, the fourth in the module's chain.

Stimulus. A clean walk; a failing transfer at each of the four read stages; a bus reset at each stage; a device whose control packet size is the floor and one whose is much larger; a configuration whose subtree is small and one large enough that the length genuinely matters; and a re-walk after a completed one.

Two stimulus requirements §7 showed are not optional:

  • Generate failures at every stage, not just the first. Without failing transfers, N2 is invisible, and a walk sequencer that treats failure as success is one of the more common defects in this kind of logic.
  • Vary the learned values between runs. A testbench that always returns the same packet size cannot distinguish a design that uses the learned value from one that uses a constant that happens to match it. W3 is unfalsifiable against a constant stimulus.

Observation. The requested length at each stage, paired with the stage itself — not merely whether the walk completed. §7's whole point is that a wrong walk completes correctly.

Reference model. The stage sequence and the expected requested length at each stage. Small, and Chapter 6.6 absorbs it into the full enumeration model.

Representative coverage — crosses:

  • stage × transfer result, all four stages against both completion and failure
  • stage × bus reset, so the abandon path is exercised from every point
  • learned packet size at the floor × learned packet size well above it
  • configuration subtree small × large
  • re-walk after DW_DONE × walk from DW_IDLE

Negative cases with defined outcomes: a failure must leave the stage unchanged and the walk retryable; a bus reset must clear every learned value rather than leaving a stale one to be used after re-addressing.

9. Common Misconceptions

10. Reason It Through

A device controller is being brought up. Enumeration succeeds against every host and device on the bench. In the field, one class of device fails to enumerate — and every failing device turns out to be a low-speed one.

What should you suspect first? Something that works for large control packet sizes and fails for small ones. The prefix read is the obvious candidate: a host or stack that guesses a modern packet size rather than using the floor will over-read a low-speed device, whose control endpoint cannot deliver that much in one packet.

Why did the bench not catch it? Because the bench had no device at the floor. §7's N1 mutant is exactly this defect, and it passes every functional check — the walk completes, the values are right — as long as the guess happens to be correct.

What is the minimal stimulus that would have caught it? One low-speed device. Or, without any hardware at all, W2 — which forbids a prefix read from using anything but the floor, and fires on the very first request regardless of what device is attached.

What does that contrast teach? That the assertion is cheaper and stronger than the coverage that would have found the same bug. Finding it by stimulus requires owning the right device and thinking to test it; finding it by property requires only stating the rule the design must obey. This is the argument for assertions in one example.

And the second-order lesson? The failing class was not random. Every failing device is low-speed is a pattern that points at a size or timing assumption, and reading a symptom distribution that way is a large part of post-silicon debugging.

11. Understanding Check

12. Summary

The host must learn a parameter that lives inside the data that parameter governs — the third circularity this module has resolved. The resolution is a guaranteed floor: the smallest control packet size any device may have is 8 bytes, so a read bounded by 8 is safe without any knowledge of the device, and the specification places the real packet size inside that prefix.

The same shape occurs twice. Requesting a configuration returns the configuration and its interfaces and endpoints as one contiguous block, whose size depends on its contents — so the host reads a short prefix, learns a total length, and re-reads properly. Seeing the pattern twice turns a memorised quirk into a reasoning tool, and explains the doubled requests that look like retries in a trace but are not.

The order is forced, not conventional: the host cannot ask for a configuration by number without first learning how many exist, and that count is in the device description. Every step is possible only because of what the previous one revealed — the principle the whole module runs on.

In hardware that becomes a gated sequencer: each full read is permitted only once its prefix read has supplied a length, prefix reads use the floor, failures do not advance, and a bus reset abandons everything learned.

Mutation testing showed why the assertions are written about the gate rather than the outcome. A sequencer that skips the prefix read and guesses completes the walk with every final value correct — and fails in the field only on devices whose packet size differs from the guess. And a sequencer that sets its validity flag on a failed transfer satisfies the gating property while gating on garbage, which is why the condition that sets a flag needs its own property.

13. What Comes Next

The host now knows what the device is, what it can do, and what it offers. The device is addressed and described.

It is still not usable.

Chapter 6.5 is the step that changes that, and the distinction it turns on is sharper than it first appears: addressed is not configured. A described device has told the host what it could do; it has not been told which of those things it should do, and until it is, its endpoints beyond the control endpoint do not operate. That selection is a commit boundary of its own, with the same structure as Chapter 6.3's — and, as it turns out, a way of undoing itself that has no analogue in addressing.

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.