Skip to content
VLSI Mentor

USB · Module 12

Data Stage

The rule that ends a transfer is not a byte count. Why a short packet terminates, why a zero-length packet is a message, and the guard no legal stimulus can test.

Chapter 12.1 opened a context. This is the stage where something moves through it.

Its subject is not the data packet — Chapter 11.2 built that. It is the question that packet cannot answer on its own: when is the transfer over?

And the answer is not a byte count.

1. Two Lengths, and Only One of Them Is on the Wire

Two different quantities are easy to conflate and mean quite different things.

A packet's length is what the data stage moved this time. It is bounded by the endpoint's wMaxPacketSize (Chapter 7.4), and it is a property of the wire.

A transfer's length is what the software on either side wanted to move. It can be zero, or megabytes. It appears nowhere in any packet.

So a transfer is a sequence of packets, and nothing in the protocol says how many. Which raises the question the whole chapter answers:

How does the receiving end know it has the last one?

Not from a length field — there is none. Not from a count — the two ends never exchanged one. The answer is a rule about packet size:

A packet shorter than wMaxPacketSize is the last one.

2. Short Means Last

The rule in full, and it is short enough to state completely:

  • A full-size packet means more follows. The sender filled the packet because it had more than would fit.
  • A packet shorter than wMaxPacketSize means that is all. The sender stopped because it ran out, and the shortfall is the signal.

The elegance is that the terminator costs nothing. No extra field, no count, no end marker — the information is carried by a length that had to be transmitted anyway.

And the cost is one awkward case, which §3 is about.

3. The Zero-Length Packet

The awkward case: a transfer whose length is an exact multiple of wMaxPacketSize has no short packet in it.

A 128-byte transfer on a 64-byte endpoint is two full packets. Both say more follows. The receiver has no way to know the second was the last, and will wait for a third that is never coming.

The fix is to send a packet of zero bytes. Zero is shorter than wMaxPacketSize, so it is short, so it terminates — and it carries no data because there is none left to carry.

A zero-length packet is not an empty packet. It is a terminator, and it is the only packet whose entire meaning is its length.

The Linux kernel carries this as a per-transfer flag:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#define URB_ZERO_PACKET   0x0040  /* Finish bulk OUT with short packet */

with the documentation spelling out the reasoning: bulk OUT transfers using it “should always terminate with a short packet, even if it means adding an extra zero length packet.”

Note that it is a flag and not automatic, which tells you something about the layering: whether a transfer needs terminating is a property of the protocol running on top of it, not of USB. A protocol whose messages carry their own lengths does not need it; one that infers message boundaries from transfer boundaries does.

4. Short Is Not an Error — Except When It Is

A short packet arriving before the receiver's byte count is satisfied is not a protocol error. It is the device saying that is all I have, which is exactly what the rule is for.

Whether it is an application error is a different question, and a layer higher up. The kernel makes the split explicit with a second flag:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#define URB_SHORT_NOT_OK  0x0001  /* report short reads as errors */

And the documentation adds the asymmetry: the flag is “invalid for write requests” — it applies to reads only.

Which makes sense once stated: on a read the device decides how much to send, so a shortfall is information about the device. On a write the host decides, so a short packet is something the host chose and cannot be surprised by.

A sequence diagram of three bulk transfers on an endpoint whose maximum packet size is sixty-four bytes. In the first transfer of one hundred and fifty bytes, the device sends two full sixty-four byte packets, each meaning more follows, and then a twenty-two byte packet which is shorter than the maximum and therefore ends the transfer. In the second transfer of exactly one hundred and twenty-eight bytes, two full packets are sent and neither is short, so the receiver cannot tell the transfer has ended; the sender therefore adds a zero-length packet, which is shorter than the maximum and terminates it. In the third transfer the host requests two hundred and fifty-six bytes but the device has only one hundred; it sends one full packet and then a thirty-six byte short packet, which ends the transfer with the request unsatisfied. An annotation notes that this last case is information rather than an error, and that only a caller which required the full count turns it into one.Three ways a transfer endsHostDevice endpoint64 B — full: morefollows64 B — full: morefollows22 B — SHORT: thatis alltransfer 1 complete— 150 B64 B — full64 B — full, andthis was the lastdata…both were full. isthere more?0 B — zero-lengthterminatortransfer 2 complete— 128 B64 B — full (hostasked for 256)36 B — SHORT, andonly 100 deliveredcomplete at 100 B:information, not anerror
Figure 1 — three transfers on a 64-byte endpoint. The first ends naturally on a short packet. The second is an exact multiple and needs a deliberate zero-length terminator. The third ends short before the host's request was satisfied, which is information rather than an error.

5. The Data Stage Controller, as RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_data_stage_ctl
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models the
// TERMINATION rules of sections 2 to 4 -- not the datapath.
//
// WHAT IT MODELS. Given one completed packet and what the transfer still
// wants, is the transfer over? Is this packet short? Was it short too
// early? Does an explicit terminator still have to be sent?
//
// WHAT IT DOES NOT MODEL. The payload or its movement (Chapter 9.5's
// buffers); the data packet itself or its toggle (Chapter 11.2); the CRC16
// (Chapter 11.6); the context that says which endpoint and direction
// (Chapter 12.1 -- consumed here as inputs); the handshake that follows
// (Chapter 12.3); and the queue of transfers this one belongs to.
//
// ── ON `pkt_oversize` ───────────────────────────────────────────────────
// A packet LONGER than wMaxPacketSize is a protocol violation -- the sender
// is not allowed to produce one. It is therefore neither short nor full,
// and it needs its own output rather than being folded into either. The
// reason is not tidiness: the buffer that received it was sized for maxp,
// so "oversize" is the only signal that can distinguish a transfer ending
// from a buffer overrunning. Section 6's M1 folds it into "full size", and
// section 8 is about why no well-behaved bench ever notices.
// ─────────────────────────────────────────────────────────────────────────
module usb_data_stage_ctl #(
  parameter int unsigned MAXP_W = 11    // wMaxPacketSize is 11 bits (Ch 7.4)
)(
  input  logic                clk,
  input  logic                rst_n,
  input  logic                bus_reset,

  // Chapter 12.1's context.
  input  logic                txn_open,
  input  logic                ctx_is_in,

  input  logic [MAXP_W-1:0]   maxp,      // this endpoint's wMaxPacketSize

  input  logic                pkt_done,  // a data packet completed
  input  logic [MAXP_W-1:0]   pkt_len,   // and this is how long it was

  // How many bytes the TRANSFER still wants. Section 1: this exists only in
  // software on each side and appears in no packet.
  input  logic [15:0]         residual,

  // The kernel's URB_ZERO_PACKET (section 3): this transfer's protocol needs
  // an explicit terminator when the byte count is an exact multiple of maxp.
  input  logic                want_zlp,

  output logic                pkt_is_short,
  output logic                pkt_is_zlp,
  output logic                transfer_complete,
  output logic                short_underrun,   // short, and bytes still wanted
  output logic                send_zlp_next,    // OUT: emit the terminator
  output logic                pkt_oversize      // babble -- see the header
);

  logic full_size;

  // See the header. These three are mutually exclusive by construction, and
  // section 7 asserts that they are.
  assign pkt_oversize = pkt_done && (pkt_len >  maxp);
  assign full_size    =             (pkt_len == maxp);
  assign pkt_is_short = pkt_done && !full_size && !pkt_oversize;

  assign pkt_is_zlp   = pkt_done && (pkt_len == '0);

  // ── SECTION 2'S RULE ────────────────────────────────────────────────────
  // A short packet ends the transfer WHATEVER the residual says -- that is
  // the whole point, and section 6's M3 is what replacing it with a
  // length-only test costs. The second term handles the transfer that ends
  // exactly on a full packet, which is complete only if no terminator is
  // still owed.
  assign transfer_complete = pkt_done &&
       ( pkt_is_short || (residual <= {5'd0, pkt_len} && !send_zlp_next) );

  // Section 4: NOT an error here. It is the device saying "that is all",
  // reported so that a caller which required the full count -- the kernel's
  // URB_SHORT_NOT_OK -- can turn it into one. Deciding that is not this
  // block's business, and pretending it is would put policy in the datapath.
  assign short_underrun = pkt_is_short && (residual > {5'd0, pkt_len});

  // Section 3: an OUT transfer that is an exact multiple of maxp contains no
  // short packet, so the receiver cannot tell it is over. Only when the
  // transfer's protocol asked for termination (`want_zlp`).
  assign send_zlp_next = pkt_done && !ctx_is_in && want_zlp && full_size &&
                         (residual == {5'd0, pkt_len});

endmodule

What it models. The termination decision for one data stage.

Engineering reason. Because a transfer's length is not on the wire, so its end has to be inferred — and the inference rule is the one thing both ends must agree on exactly.

Inputs. The context, the endpoint's maximum packet size, a completed packet with its length, the transfer's residual, and whether an explicit terminator was requested.

State retained. None. Every output is a function of this packet and the current residual; the residual itself is held by whatever owns the transfer.

Outputs. Three classifications of the packet, the completion verdict, the early-short report, and the terminator request.

Hardware implied. Two comparators against maxp, one against the residual, and a little combining logic.

Reset behaviour. Nothing to reset.

Assumptions. That pkt_len counts payload bytes only — not the PID or the CRC, which would shift every comparison by three; that residual is the count before this packet; and that maxp is the value from the active configuration's endpoint descriptor rather than a default.

Omissions. The payload, the buffers, the packet, the CRC, the handshake and the transfer queue — in the header.

What DV should verify. That a short packet always completes the transfer; that an exact-multiple transfer with want_zlp does not complete without the terminator; that an early short packet is reported and not treated as an error; that an oversized packet is flagged and classified as neither short nor full; and that a zero-length packet is short.

Three transfers, three terminations

8 cycles
A waveform of the data stage controller over eight completed packets on a sixty-four byte endpoint. The first two packets of transfer A are sixty-four bytes with residuals of one hundred and fifty and eighty-six, neither short nor complete. The third is twenty-two bytes, which is short and completes the transfer. Transfer B's first packet is sixty-four bytes with a residual of one hundred and twenty-eight. Its second is sixty-four bytes with a residual of sixty-four, which exactly exhausts the request but is not short, so instead of completing it asserts the request for a zero-length terminator. The third packet is zero bytes, which is short and completes the transfer. Transfer C's first packet is sixty-four bytes against a residual of two hundred and fifty-six. Its second is thirty-six bytes, which is short and completes the transfer even though one hundred and ninety-two bytes were still wanted.short — transfer A endsshort — transfer A endsexact multiple: terminator owedexact multiple: terminatorowedthe ZLP ends transfer Bthe ZLP ends transfer Bshort early — ends, and reports itshort early — ends, andreports itpacketA1A2A3B1B2B3C1C2pkt_len646422646406436residual1508622128640256192is_shortcompletezlp_nextt0t1t2t3t4t5t6t7
Figure 2 — eight packets spanning three transfers, every column taken from a simulation of the block above. Transfer A ends on a natural short packet; B is an exact multiple, so the full packet at column 4 requests a terminator rather than completing; C ends short with the host's request unsatisfied, which completes the transfer and raises the early-short report.

6. Mutation Test

Five mutations over 866 packets across 411 completed transfers, with packet sizes, transfer lengths, directions and terminator requests randomised — plus directed cases for every relationship between transfer length and wMaxPacketSize, and six deliberately oversized packets.

The unmutated block: 397 short packets, 183 early-short reports, 8 terminating ZLPs, 6 oversized packets all flagged, and 0 errors of every kind.

packets (transfers done)oversize unflaggedshort wrongcomplete wrongunderrun wrongZLP wrong
golden866 (411)00000
M1 oversize reads as full866 (411)60000
M2 short never noticed15,196 (228)014,72714,51314,513183
M3 length-only completion15,188 (228)0014,5210183
M4 no early-short report866 (411)0001830
M5 no ZLP terminator858 (411)00808

Read the packet-count column first. M2 and M3 raise it from 866 to over 15,000 while completing fewer transfers — 228 instead of 411. Transfers stopped ending. The bench's loop guard was what stopped them, not the design.

M1 — treat an oversized packet as full-size

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign full_size = (pkt_len >= maxp);   // MUTANT M1

Measured: all 6 oversized packets unflagged. Everything else identical to the golden design — 866 packets, 411 transfers, zero errors of every other kind.

And it escaped the bench entirely until deliberately illegal stimulus was added. §8.

The hazard is a buffer, not a length. A packet longer than wMaxPacketSize was received into storage sized for wMaxPacketSize — Chapter 9.5 sized it from exactly that number. Treating the packet as full means the transfer continues normally and nothing anywhere records that the buffer was overrun.

M2 — never classify a packet as short

Measured: 14,727 misclassifications, and the packet count explodes from 866 to 15,196 while completions fall from 411 to 228.

§2's rule removed entirely. Every transfer runs until the bench's guard stops it, because the only signal that a transfer has ended is the one that was deleted.

This is the loud one, and it is included as the contrast for M3 — same catastrophic outcome, immediately visible in any test that checks a transfer ever finishes.

M3 — complete on the byte count instead

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign transfer_complete = pkt_done && (residual <= pkt_len);   // MUTANT M3

Measured: 14,521 wrong completions, packet count 15,188, completions 228.

The mutation that a reasonable engineer writes. The transfer is complete when we have moved everything we asked for is a true sentence about most transfers and the wrong rule for the protocol.

It fails on exactly the case the short-packet rule exists for: the device has less than the host asked for. The residual never reaches zero, the short packet is ignored, and the transfer waits forever for bytes the device does not have.

Note also that pkt_is_short is perfectly correct under M3 — 0 errors. The classification works; the use of it is gone. A bench checking that short packets are identified passes completely.

M4 — do not report an early short packet

Measured: 183 unreported, and every other output correct.

No data is lost and no transfer misbehaves. What is lost is the information a layer above needs — the kernel's URB_SHORT_NOT_OK cannot be implemented by a caller that is never told the read was short.

The transfer layer is right and the application layer is blind, which is §4's split arriving as a missing wire.

M5 — omit the zero-length terminator

Measured: 8 wrong completions and 8 missing terminators — and nothing else.

Eight is a small number and a misleading one. In random stimulus, transfer lengths that are exact multiples of wMaxPacketSize are rare.

In real software they are the common case. A filesystem reads in 512-byte or 4096-byte blocks. A frame buffer is a round number. The lengths real software chooses are overwhelmingly powers of two, and wMaxPacketSize is always a power of two — so exact multiple is close to the normal condition, not an edge case.

Random stimulus made this defect rare; production makes it universal.

7. The Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Classification: TEACHING ASSERTIONS about data-stage termination.
// P-properties classify the packet; T the termination rule; R the reports.
// ─────────────────────────────────────────────────────────────────────────

// P1 -- THE THREE CLASSIFICATIONS ARE EXCLUSIVE AND EXHAUSTIVE. A completed
// packet is short, exactly full, or oversized -- never two, never none.
// Catches section 6's M1, which makes "oversized" vanish into "full".
property p_classification_onehot;
  @(posedge clk) disable iff (!rst_n)
    pkt_done |-> $onehot({pkt_is_short, (pkt_len == maxp), pkt_oversize});
endproperty
assert property (p_classification_onehot);

// P2 -- SHORT IS EXACTLY "LESS THAN MAXP". An equivalence, sourced from the
// protocol's definition rather than from the RTL's expression of it.
property p_short_iff_under_maxp;
  @(posedge clk) disable iff (!rst_n)
    pkt_done |-> (pkt_is_short == (pkt_len < maxp));
endproperty
assert property (p_short_iff_under_maxp);

// T1 -- A SHORT PACKET ALWAYS ENDS THE TRANSFER. Section 2's rule, and the
// ONLY property that catches section 6's M3 -- whose packet classification
// is flawless and whose USE of it is gone.
property p_short_terminates;
  @(posedge clk) disable iff (!rst_n)
    pkt_is_short |-> transfer_complete;
endproperty
assert property (p_short_terminates);

// T2 -- AND A FULL PACKET WITH A TERMINATOR OWED DOES NOT. Section 3, and
// section 6's M5. Without this, a design that completes on every packet
// satisfies T1.
property p_owed_terminator_blocks;
  @(posedge clk) disable iff (!rst_n)
    send_zlp_next |-> !transfer_complete;
endproperty
assert property (p_owed_terminator_blocks);

// T3 -- AND NOTHING ELSE COMPLETES A TRANSFER. The closing half: completion
// requires either a short packet or an exhausted residual with nothing owed.
property p_complete_has_a_cause;
  @(posedge clk) disable iff (!rst_n)
    transfer_complete |->
      (pkt_is_short || (residual <= {5'd0, pkt_len} && !send_zlp_next));
endproperty
assert property (p_complete_has_a_cause);

// R1 -- AN EARLY SHORT PACKET IS REPORTED, AND ONLY THOSE. Section 6's M4.
// An equivalence, because "reported too often" is as wrong as "not reported"
// -- a caller that sees spurious short reads will reject good transfers.
property p_underrun_iff_early_short;
  @(posedge clk) disable iff (!rst_n)
    short_underrun == (pkt_is_short && (residual > {5'd0, pkt_len}));
endproperty
assert property (p_underrun_iff_early_short);

// R2 -- AN EARLY SHORT PACKET STILL COMPLETES THE TRANSFER. Section 4's
// distinction, stated so that it cannot quietly become an error: whether it
// is a problem is the CALLER's judgement, and this layer's job is to end the
// transfer and say what happened.
property p_underrun_still_completes;
  @(posedge clk) disable iff (!rst_n)
    short_underrun |-> transfer_complete;
endproperty
assert property (p_underrun_still_completes);

// Z1 -- A ZERO-LENGTH PACKET IS SHORT. Trivial, and worth stating because a
// design that special-cases length zero somewhere will break it.
property p_zlp_is_short;
  @(posedge clk) disable iff (!rst_n)
    (pkt_done && pkt_is_zlp && (maxp != '0)) |-> pkt_is_short;
endproperty
assert property (p_zlp_is_short);

T1 is the chapter's property, and its shape is the lesson. It says nothing about lengths, counts or residuals — just that a short packet ends the transfer, unconditionally. §6's M3 satisfies every property about classification and fails this one alone.

P1's $onehot is what catches M1, and it does so without the bench having to generate an oversized packet — because it constrains the relationship between the three outputs rather than their values. A formal tool would find M1 from this property in seconds; a simulation needs §8's illegal stimulus. That difference is worth noticing when deciding what to prove and what to run.

8. Verification

This chapter's commit point is the transfer ended when the protocol says it ended, and the layer above was told what happened.

Stimulus. Directed cases for every relationship between transfer length and wMaxPacketSize: an exact single packet; an exact multiple with and without a terminator request; a natural short ending; a single short packet; a zero-length transfer; an IN where the device has less than requested; maxp equal to the transfer length; a large maxp; six oversized packets; and 400 randomised transfers over packet sizes 8 to 64.

Observation. All six outputs per packet, plus two running counts: packets issued and transfers completed. The ratio is what makes M2 and M3 unmistakable — the design that never terminates does not produce wrong outputs so much as more of them.

Reference model. The four termination rules from §§2–4, written from the rules. It is more independent than most in this module because the rules are short enough to state as sentences, and each expectation is one of those sentences.

And two bench defects were found and fixed before the mutation results above were trustworthy:

  • $random is signed in Verilog, so $random % 4 yields values in −3…3 — which made the randomised wMaxPacketSize collapse to 1 and 2 instead of 8 to 64. {$random} makes it unsigned.
  • The transfer loop had two termination conditions, its own and the design's, and they disagreed — letting moved exceed the requested length and driving a negative residual into a 16-bit unsigned port. A bench that decides for itself when a transfer is over is not testing the design's decision.

Coverage — crosses:

  • transfer length modulo maxp: 0 (exact multiple), 1, and mid-range
  • want_zlp × exact multiple × direction — the cell that produces a terminator
  • IN where the device has more, exactly, and less than requested
  • packet length: 0, 1, maxp − 1, maxp, maxp + 1
  • maxp values 8, 16, 32, 64, 512

Negative cases with defined outcomes: a short packet never fails to complete; a transfer with a terminator owed never completes early; an early short packet is never reported as an error at this layer; and an oversized packet is never classified as short or full.

9. Debugging: the Read That Hangs Only on Short Responses

A device works for fixed-size data. A status read that returns a variable number of bytes hangs — the request never completes and never fails, and the application times out. Larger status responses work; small ones hang.

What does larger works, smaller hangs tell you? That the failure depends on the relationship between what was requested and what was returned. The device is fine; the difference is a shortfall.

Which two rules could produce that? §6's M3 — completion decided by byte count rather than by a short packet — and a device that simply stops sending without a short packet at all.

How do you tell them apart in one observation? Look at the wire. If the device sent a short packet and the host kept asking, the host's termination rule is wrong. If the device sent nothing after its last full packet, the device failed to terminate.

Why does it hang rather than fail? Because neither end thinks anything is wrong. The device answered fully and stopped; the host is waiting for data it believes is still coming. A hang is what two correct-looking parties disagreeing about termination produces — Chapter 11.2 §9 described the same shape from the toggle's side.

What if the response length happens to be an exact multiple? Then the device is the one that owes a terminator — §3 — and the symptom is identical from the host's point of view. The same hang has two causes on opposite sides of the bus, and only the trace distinguishes them.

What is the fastest experiment? Request one byte more than the device will return. If it now completes, the short-packet path works and the original failure was an exact-multiple terminator. A one-byte change that fixes a hang is diagnostic of a termination rule, not of a data problem.

The signature to keep: a transfer that hangs only when the response is shorter than the request is a termination-rule disagreement — and the side that is wrong is whichever one ignored a short packet.

10. Common Misconceptions

11. Reason It Through

A device implements a vendor protocol in which every message is a 64-byte header followed by a payload. Its endpoint's wMaxPacketSize is 64. During bring-up on a full-speed link everything works. The same firmware on a high-speed link, where wMaxPacketSize becomes 512, hangs on every message.

What changed? Only wMaxPacketSize. The firmware, the protocol and the message sizes are identical.

What does a 64-byte message look like at each speed? At maxp 64 it is exactly one full packet. At maxp 512 it is a 64-byte short packet.

So which behaviour is the firmware relying on? It worked when the message was full-size and hangs when it is short — which is backwards from the usual failure. A short packet terminates, so the high-speed case should be the easy one.

Unless the receiving side is counting packets. If the host software expects one packet per message, then at maxp 64 it gets one and at maxp 512 it also gets one — that cannot be it either.

So look at the other direction. At maxp 64 a 64-byte message is an exact multiple, so it needs a zero-length terminator — §3. If the firmware sends one, the high-speed case now sends a ZLP after a packet that was already short, and a second short packet is a second transfer's worth of nothing. The receiver sees a spurious empty message.

Which is the more likely fault? That the firmware hard-coded the terminator decision instead of deriving it from wMaxPacketSize. The rule is conditional on maxp, and maxp is not a constant — it is read from the endpoint descriptor, which differs by speed (Chapter 7.4).

What is the general error? Treating a configuration-time value as a compile-time constant. The firmware encoded a consequence of maxp = 64 rather than the rule that produced it.

And how would you have caught it? By testing at both speeds — which is obvious in hindsight and routinely skipped, because the firmware is identical and the link is supposed to be transparent. The link is transparent; wMaxPacketSize is not.

12. Understanding Check

13. Summary

A transfer's length is nowhere on the wire. Only a packet's length is, so the end of a transfer must be inferred — and the rule is that a packet shorter than wMaxPacketSize is the last one.

The terminator is free because it rides on a property the packet already has rather than a value it contains — the same structural move as Chapter 11.2's toggle living in the PID.

The cost is one awkward case: a transfer that is an exact multiple of wMaxPacketSize contains no short packet, so it needs a zero-length packet — a terminator, not an empty packet, and one the kernel exposes as an explicit per-transfer flag because whether a transfer needs terminating is a property of the protocol above USB.

A short packet arriving early is information, not an error. The kernel's URB_SHORT_NOT_OK turns it into one at the caller's request, and is invalid for writes — because on a write the host chose the length. And the kernel carries a quirk bit for host controllers that fail to stop the endpoint queue on a short transfer, which is evidence both that the rule is real and that shipping silicon has got it wrong.

§6 measured five mutations over 866 packets and 411 transfers:

  • Treating an oversized packet as full changed nothing measurable and lets a buffer overrun go unrecorded.
  • Never classifying short and completing on byte count both stopped transfers ending — packet counts rose past 15,000 while completions fell to 228 — but the second classifies short packets flawlessly and merely ignores them, so it works in bring-up and hangs on the first variable-length response.
  • Omitting the early-short report costs no data and blinds the layer that must decide whether a short read matters.
  • Omitting the ZLP terminator produced 8 failures in random stimulus and would produce them constantly in production, where transfer lengths are powers of two.

And §8 adds a fifth shape to the catalogue of benches that pass wrongly:

A guard against a protocol violation is, by construction, untestable by a bench that obeys the protocol.

The escape is either a bench that misbehaves on purpose, or — better — a property constraining the outputs' relationship rather than their values, which catches it with no stimulus at all. When your bench cannot generate the input, prove the guard instead of simulating it.

14. What Comes Next

Data has moved. Nobody has said whether it arrived.

Chapter 12.3 is the Handshake Stage, and its subject is not the four packets — Chapter 11.3 built those. It is who transmits it, which changes with the token's direction: on an OUT the device answers, and on an IN the host does. The bus turns around in opposite directions in the two cases, and one transfer type has no handshake stage at all.

That asymmetry is where a device's transmit and receive paths have to agree about which of them owns the bus next — and Chapter 12.5 is about how little time they have to decide.

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.