Skip to content
VLSI Mentor

USB · Module 10

Isochronous Transfers

The only retryless transfer type, and the only one for which that is an advantage. Freshness as a first-class property, why a stale sample beats no sample only in the wrong direction, and the conservation law that passes on a broken design.

Three types down, and all three share an assumption so fundamental that none of them named it: that a failed transfer is worth doing again.

This one does not share it. Isochronous is the only USB transfer type with no retry mechanism — not retry is discouraged, not retry is limited, but no mechanism by which a retry could occur. A corrupted isochronous packet is simply gone.

That reads like a weakness. It is a deliberate design, and the chapter is about why.

1. Late Data Is Worse Than Absent Data

The premise the other three types never had to examine.

Consider what a retry costs on a stream with a deadline. An audio device is playing samples at a fixed rate. A packet is corrupted. A retry would:

  • consume bandwidth that the next interval's data needs,
  • arrive after its moment has passed, because the deadline for that audio was the instant it should have been played,
  • and deliver data that is now wrong — not corrupted, but for the wrong time.

So the retry spends real capacity to deliver something useless, and risks the next packet in order to do it. The correct response to a lost sample in a real-time stream is to give up on it and keep the stream running.

2. What Isochronous Buys, and What It Pays

It buys a reservation, and a stronger one than Interrupt's. Chapter 10.4 §2 established that an interrupt endpoint buys a bound on the gap between questions. An isochronous endpoint buys that plus a committed amount of capacity in each interval — enough for its declared packet size, held from configuration until un-configuration.

It pays with delivery. Nothing guarantees any particular packet arrives. Chapter 10.1 §2's grid put Isochronous alone in the retry is not useful column, and this is the cell's consequence: the reservation guarantees the opportunity and the capacity, never the data.

Which produces a guarantee shape no other type has:

The bandwidth is guaranteed. The bytes are not.

And that is the right trade for a stream, because a stream's requirement is that the next interval's data arrives on time, not that every interval's data arrives. A missing audio sample is a click. A late one, or one that displaced its successor, is a longer and worse artefact.

3. How the Descriptor Says So

Chapter 7.4's endpoint descriptor carries all of it, and the encoding is worth having in front of you because it is where the four types actually become distinguishable to software.

The transfer type is bmAttributes[1:0], and the kernel's values are the specification's:

TypebmAttributes[1:0]
Control0
Isochronous1
Bulk2
Interrupt3

Isochronous endpoints use more of bmAttributes than any other type, because a stream needs to say how it is clocked. Bits [3:2] are the synchronisation type — USB_ENDPOINT_SYNC_NONE, _ASYNC, _ADAPTIVE, _SYNC — and bits [5:4] are the usage type: data, feedback, or implicit-feedback data.

That second field exists because of a problem unique to streams. A device's sample clock and the host's are independent crystals; they will drift. Feedback endpoints are how a stream reports its true rate so the other end can adjust — and the fact that USB needed a whole endpoint usage type for it is the measure of how hard the problem is.

And wMaxPacketSize means more here too. For high-speed periodic endpoints the field is not just a size: usb_endpoint_maxp_mult returns wMaxPacketSize[12:11] + 1, which the kernel documents as the endpoint's transactional opportunities — up to three transactions per interval instead of one. A high-speed isochronous endpoint can therefore reserve up to three times its packet size per microframe, and a device that ignores those two bits reads the size correctly and the capacity wrong.

4. The Obligation This Creates for the Device

No retry means no second chance, and that turns a device-side timing problem into a correctness problem.

The device must have current data ready when its slot arrives. Not eventually — then. A slot is an appointment, and an appointment missed is not rescheduled.

And when the device is not ready, it has exactly two options, which are not equally good:

  • Send nothing. The consumer sees a gap, knows there is a gap, and can conceal it — interpolate, repeat at the application layer, mute. The failure is visible and local.
  • Send what it has anyway. The consumer receives a well-formed packet of plausible data and has no way to know it is old. The failure is invisible and propagates.

A gap is a known unknown. A stale sample is an unknown wrong.

This is why §5's block exists, and why it is a freshness model rather than a buffer: its entire job is to refuse the second option. And §6 measures what happens when that refusal is removed — including the specific, unpleasant way the failure hides.

A sequence diagram of five isochronous service slots. In the first slot the producer has supplied a fresh sample and the endpoint delivers it to the host. In the second slot no new sample has arrived but the held sample is still within its freshness window, so it is delivered. In the third slot the held sample has exceeded its freshness window and become stale, so the endpoint delivers nothing and reports an underrun; the host receives a gap rather than old data. In the fourth slot a packet is corrupted in transit, and no retry occurs because isochronous has no retry mechanism; the slot is simply lost. In the fifth slot the producer has recovered and a fresh sample is delivered. An annotation notes that the stream never stops and never reschedules anything.A stream through a producer stallProducerISO endpointHostfresh sampleslot 1 — deliveredslot 2 — heldsample, still freshproducer has stalled· sample now pastits windowslot 3 — NOTHINGsent · underruna gap the consumercan concealslot 4 — corruptedin transitno retry exists ·the slot is gonefresh sampleslot 5 — deliveredthe stream neverstopped andrescheduled nothing
Figure 1 — five consecutive isochronous slots. The stream survives a producer stall by delivering nothing for two slots rather than delivering something old, and recovers on the next fresh sample. Note the slot that is simply lost: nothing retries it, and the stream does not stop to mourn it.

5. The Freshness Model, as RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// usb_iso_freshness
//
// Classification: SIMPLIFIED SYNTHESIZABLE TEACHING RTL. It models section
// 4's obligation and nothing else: whether what the device holds is still
// current enough to send, and what to do when it is not.
//
// WHAT IT MODELS. The AGE of the held sample in service slots, the point at
// which age makes it unsendable, and the reporting of a slot that passed
// with nothing fresh to send.
//
// WHAT IT DOES NOT MODEL. The sample itself (this block tracks freshness,
// never content); the buffer holding it (Chapter 9.5); the packet (Module
// 11); the transaction, which for isochronous has no handshake stage at all
// (Module 12); the (micro)frame structure that generates `slot_tick`; clock
// drift between producer and host, which is what the descriptor's
// synchronisation type in section 3 exists to manage; and the bandwidth
// reservation, which is a configuration-time negotiation this block never
// sees.
//
// ── THE CONTRACT, STATED BEFORE THE CODE ────────────────────────────────
//   HOLD, do not consume. A sample stays usable for MAX_AGE slots after it
//   is produced, and may be sent more than once within that window. This is
//   correct when the producer's rate and the service rate are independent --
//   which for isochronous they always are (section 3's synchronisation
//   types exist precisely because they are). Section 6's Z5 measures the
//   cost of the other policy.
//
//   NEVER SEND STALE. Once age exceeds MAX_AGE the sample is unsendable and
//   the slot underruns instead. Section 4: a gap is a known unknown, a
//   stale sample is an unknown wrong. This is the rule the whole block is
//   built around, and section 6's Z1 measures what its removal costs.
// ─────────────────────────────────────────────────────────────────────────
module usb_iso_freshness #(
  // How many slot boundaries a sample survives. MAX_AGE = 2 means "good for
  // the slot it was produced in and the two after it".
  parameter int unsigned MAX_AGE = 2
)(
  input  logic clk,
  input  logic rst_n,

  // The stream exists: the endpoint is enabled by the active configuration
  // (Chapter 9.3) and its reservation is held (Chapter 8.5).
  input  logic stream_active,

  // A service slot boundary -- one (micro)frame. Host timing.
  input  logic slot_tick,

  // The producer has supplied a new sample. Device timing, and unrelated to
  // slot_tick: see the header's note on synchronisation types.
  input  logic producer_valid,

  // This slot's transfer is happening now.
  input  logic consume,

  output logic data_valid,  // there is something CURRENT to send
  output logic stale,       // what is held is past its deadline
  output logic underrun     // a slot passed with nothing current to send
);

  localparam int unsigned AGE_W = $clog2(MAX_AGE + 2);

  logic             held_q;      // a sample is present
  logic [AGE_W-1:0] age_q;       // slot boundaries since it was produced
  logic             underrun_q;

  // STALENESS IS DERIVED, NOT STORED. Chapter 8.4's rule: a derived signal
  // cannot disagree with its source, and a stored copy would create one
  // obligation per transition. Age is the only truth here.
  assign stale      = held_q && (age_q > MAX_AGE);
  assign data_valid = held_q && !stale;
  assign underrun   = underrun_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      held_q <= 1'b0; age_q <= '0; underrun_q <= 1'b0;
    end else if (!stream_active) begin
      // No stream means no deadline and nothing to be fresh FOR. A sample
      // held across a teardown would be measured against a window that no
      // longer exists.
      held_q <= 1'b0; age_q <= '0; underrun_q <= 1'b0;
    end else begin
      // REGISTERED so the report is a stable fact about the slot that just
      // ended rather than a combinational shadow of this cycle's inputs.
      // Chapter 8.2 measured a mutation that only a property pinning this
      // could catch.
      underrun_q <= consume && !data_valid;

      if (producer_valid) begin
        held_q <= 1'b1;
        age_q  <= '0;               // a new sample is age zero BY DEFINITION
      end else if (slot_tick) begin
        // Saturate. Age past MAX_AGE is already unsendable, so counting
        // further buys nothing and costs a wider counter.
        if (age_q <= MAX_AGE) age_q <= age_q + 1'b1;
      end
    end
  end

endmodule

What it models. Whether the held sample is current enough to send, and whether a slot passed without one.

Engineering reason. Because §1's premise makes lateness the failure mode, and a design with no explicit notion of age has no way to refuse to be late.

Inputs. Clock and reset; the stream's existence; the slot boundary; the producer's new-sample indication; the slot's transfer.

State retained. A presence bit, a saturating age counter — 2 bits for MAX_AGE = 2 — and the registered underrun flag. Four flip-flops total.

Outputs. data_valid and stale, both derived from the age; underrun, registered.

Hardware implied. A small saturating counter, one comparator against a constant, and two flags.

Reset behaviour. Everything clears on hard reset and whenever the stream is not active. Nothing is carried across a teardown, because freshness is measured against a window that a teardown destroys.

Assumptions. That producer_valid and slot_tick are already in this clock domain — they come from genuinely independent timebases and crossing that boundary is a real obligation not modelled here; that consume coincides with the slot it refers to; and that MAX_AGE was chosen from the stream's actual tolerance rather than from convenience.

Omissions. The sample, the buffer, the packet, the transaction, the frame generator, clock drift and the reservation — the header lists them, because a block about freshness invites being read as the endpoint's data path.

What DV should verify. That a stale sample is never presented as valid; that a new sample resets the age; that age advances only on slot boundaries; that every slot consumed either delivers current data or reports an underrun; that underrun is reported for exactly those slots; and that a teardown discards what was held.

Freshness across a producer stall

10 cycles
A waveform of the isochronous freshness model over ten cycles with the stream active throughout and a maximum age of two slots. In the first cycle the producer supplies a sample and the age resets to zero. In the second cycle the slot consumes it and current data is valid. In the third and fourth cycles the producer has stalled but the sample ages only to one and then two, so it remains within its window and is delivered again. In the fifth cycle the age reaches three, the stale output asserts, valid data falls, and the slot is consumed with nothing to send. In the sixth cycle the registered underrun flag reports that slot, and the sample remains stale. In the seventh cycle the producer recovers and the age resets. In the eighth, ninth and tenth cycles fresh data is delivered as the sample ages again within its window.age 2 — still inside the windowage 2 — still inside thewindowage 3 — STALE: send nothingage 3 — STALE: send nothingunderrun reports the slot that endedunderrun reports the slotthat endedfresh sample — age resetsfresh sample — age resetsproducer_validslot_tickconsumeage0012333012data_validstaleunderrunt0t1t2t3t4t5t6t7t8t9
Figure 2 — ten cycles across a producer stall, every column taken from a simulation of the block above. The held sample survives two slot boundaries and is delivered twice; on the third it exceeds its window and the endpoint sends nothing rather than sending it. The underrun flag is registered, so it reports the slot that just ended. Controller-domain decoded events, not USB bus signalling; no real durations are depicted.

6. Mutation Test

Five mutations, run against the block in §5 over 636 cycles and 307 service slots. Every slot must account for itself:

slots consumed = fresh deliveries + stale deliveries + underruns, with stale deliveries = 0

The unmutated block: 307 slots = 249 fresh + 0 stale + 58 underruns.

freshstaleunderrunsslot conservationunderrun unreported
golden249058holds0
Z1 age never gates2493226holds0
Z2 age frozen2493226holds0
Z3 underrun not reported249058holds58
Z4 new sample does not reset age570250holds0
Z5 consume destroys the sample760231holds0

Read the conservation column first, because it is the finding. It holds under every mutation, including the two that ship stale data. §8 is about why, and it is the most important paragraph in the chapter.

Z1 — remove the age gate

Let stale be permanently false, so whatever is held is always sendable.

Measured. 32 stale deliveries where there must be zero — and underruns fell from 58 to 26. The arithmetic is exact: 32 slots that should have been visible gaps became invisible wrong answers.

That trade is the whole chapter. The mutant does not lose data; it gains data — 32 extra deliveries — and every one of them is a well-formed packet of plausible content that the consumer cannot distinguish from a correct one.

And a throughput metric would call this an improvement. Deliveries up, underruns down. Any bench measuring how much got through would score the mutant better than the correct design.

Z2 — freeze the age counter

Stop incrementing on slot_tick, so nothing ever becomes stale.

Measured: 32 stale deliveries, 26 underruns — identical to Z1, cycle for cycle.

Two different mutations, one observable failure, which Chapter 10.3 §6 also produced in a different form. There the lesson was that the same behaviour can be reached by removing a register or by mis-updating it. Here it is narrower and more practical: a check written against age would catch Z2 and miss Z1; a check written against what was sent catches both. Assert on the observable obligation, not on the mechanism that implements it.

Z3 — stop reporting underruns

Measured. Data behaviour completely unchanged: 249 fresh, 0 stale, and the bench's own count of underrun slots still 58. The only thing that fired was the direct check that the flag reports them — 58 unreported.

And unlike Chapter 10.4's I3, the missing signal here is not the only evidence. The consumer already sees the gap; a slot with no data is self-announcing. So the flag's value is not detection — it is attribution.

Without it you know the stream has gaps. With it you know they are the device's fault.

That distinction is worth two flip-flops the first time a customer reports audio dropouts and the question is whether the bus, the host, or the firmware is responsible.

Z4 — a new sample does not reset the age

Measured: fresh deliveries collapse from 249 to 57, underruns rise from 58 to 250.

The age counter saturates and never comes back, so within a few slots the stream is permanently stale and almost every slot underruns. Catastrophic, immediate, and caught by everything — which makes it the least interesting mutation here, and worth including precisely for that contrast: this is what a defect looks like when the design does make it visible.

Z5 — consume destroys the held sample

Treat the sample as a queue entry rather than a held value.

Measured: fresh deliveries 249 → 76, underruns 58 → 231.

This one is a policy change, not a coding error, and the honest verdict has to say so. Consuming is correct for a stream where every item must be delivered exactly once. Holding is correct for isochronous, because §3's synchronisation types exist precisely because the producer's clock and the service clock are independent — so a slot will regularly arrive with no new sample, and the last one is still the best available answer while it is inside its window.

Against this block's stated contract it is a defect, and the measurement gives the price: three quarters of the stream's deliveries. The contract is in the header for exactly this reason — without it, Z5 is an argument, and with it, Z5 is a number.

7. The Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Classification: TEACHING ASSERTIONS about the freshness model.
// F is the FRESHNESS contract -- the reason the block exists.
// A is AGE bookkeeping. U is UNDERRUN reporting.
// ─────────────────────────────────────────────────────────────────────────

// F1 -- NEVER SEND STALE. The one property the block exists to satisfy, and
// the only one that catches BOTH section 6's Z1 and Z2 -- because it is
// written against WHAT IS PRESENTED, not against how age is computed.
property p_never_valid_when_stale;
  @(posedge clk) disable iff (!rst_n)
    stale |-> !data_valid;
endproperty
assert property (p_never_valid_when_stale);

// F2 -- STALENESS IS EXACTLY AGE. Staleness is derived (Chapter 8.4), so
// this is not a second source of truth -- it is a statement that no OTHER
// term crept into the expression during a later edit. Chapter 9.5 measured
// why a derived signal is asserted as an EQUALITY and not an implication:
// the implication form is silently satisfied by a design that does nothing.
//
// This and A2 below reference held_q and age_q, which are internal. Bind
// this property into the module (or hoist the two signals to outputs for
// verification); an assertion you cannot connect is not an assertion.
property p_stale_iff_too_old;
  @(posedge clk) disable iff (!rst_n)
    stale == (held_q && (age_q > MAX_AGE));
endproperty
assert property (p_stale_iff_too_old);   // via bind

// A1 -- A NEW SAMPLE IS AGE ZERO. Catches section 6's Z4.
property p_new_sample_resets_age;
  @(posedge clk) disable iff (!rst_n)
    (stream_active && producer_valid) |=> data_valid;
endproperty
assert property (p_new_sample_resets_age);

// A2 -- AGE ONLY ADVANCES AT A SLOT BOUNDARY. Catches an age that drifts on
// some other event, which would make MAX_AGE mean nothing.
property p_age_advances_only_on_tick;
  @(posedge clk) disable iff (!rst_n)
    (!slot_tick && !producer_valid && stream_active) |=> $stable(age_q);
endproperty
assert property (p_age_advances_only_on_tick);   // via bind

// U1 -- EVERY BARREN SLOT IS REPORTED. Catches section 6's Z3. Note the
// |=> : the flag is registered, so the obligation lands on the NEXT cycle,
// and writing |-> here would fail on the correct design -- an off-by-one
// that would then get "fixed" by weakening the property.
property p_underrun_reported;
  @(posedge clk) disable iff (!rst_n)
    (stream_active && consume && !data_valid) |=> underrun;
endproperty
assert property (p_underrun_reported);

// U2 -- AND NO OTHERS. Without this, a flag tied high satisfies U1.
property p_no_ghost_underrun;
  @(posedge clk) disable iff (!rst_n)
    (stream_active && !(consume && !data_valid)) |=> !underrun;
endproperty
assert property (p_no_ghost_underrun);

// T1 -- A TEARDOWN DISCARDS WHAT WAS HELD. Freshness is measured against a
// window that a teardown destroys (Chapter 8.6).
property p_teardown_discards;
  @(posedge clk) disable iff (!rst_n)
    (!stream_active) |=> (!data_valid && !underrun);
endproperty
assert property (p_teardown_discards);

F1 is the property the block exists for, and its form is the lesson. It is written against what is presented to the consumer, not against the age computation — which is why one property catches two mutations that arrive at the failure by completely different routes.

U1 and U2 must be written as a pair. U1 alone is satisfied by a flag tied permanently high; U2 alone is satisfied by a flag tied permanently low. Chapter 9.5 measured what happens when only one half of such a pair exists — nothing fires at all, and the bench reports success.

And U1's |=> is deliberate. The flag is registered, so the obligation lands on the next cycle. Writing |-> would fail on the correct design — and the natural response to a property failing on correct RTL is to weaken it, which is how a bench quietly loses the only check that mattered.

8. Verification

This chapter's commit point is nothing old was ever presented as current.

Stimulus. A perfectly-fed stream; a producer stall long enough to cross the freshness boundary — the case Z1 and Z2 need, and the one a well-behaved directed test never creates; a consume with nothing ever produced; a producer and a consume in the same cycle; a teardown mid-flight; and a long randomised phase driving the producer and the slot boundary from independent distributions, because §3's whole point is that they are independent in reality.

Observation. Both flags and the age every cycle — and, critically, a classification of every slot into fresh, stale, or underrun. The classification uses the bench's own independently-tracked age, not the DUT's.

Coverage — crosses:

  • age at every value from 0 to MAX_AGE + 1, × consume
  • producer_valid × slot_tickall four combinations, including the simultaneous one
  • producer_valid arriving at each age, including exactly at the staleness boundary
  • stream_active falling at each age and with each flag state
  • consecutive underruns, and recovery from an arbitrary number of them

Negative cases with defined outcomes: data_valid is never asserted while stale is; age never advances without a slot boundary; underrun is asserted for every barren slot and no others; and nothing survives a teardown.

9. Debugging: the Stream That Sounds Fine on the Analyser

An audio device produces occasional artefacts. A protocol analyser shows a continuous, well-formed isochronous stream: every slot carries a packet, every packet is the right size, no errors are reported anywhere.

What does every slot carries a packet rule out? Underruns. If the device were sending nothing, the trace would show gaps — and it shows none. So whatever is wrong, it is not a gap.

What does that leave? That the packets are present and wrong. And in an isochronous stream there is exactly one way for a well-formed packet of valid-looking data to be wrong: it is old. §6's Z1 and Z2 produce exactly this trace.

Why will the analyser never tell you? Because the data is valid. There is no field carrying the sample's age, no checksum over its timeliness, and nothing in the packet that distinguishes a current sample from one sent three slots late. The protocol has no concept of freshness on the wire — freshness is a device-side obligation, which is exactly why §5's block exists on the device side.

How do you actually find it? By correlating against the producer, not against the bus. Instrument the point where samples are generated and compare the sequence produced with the sequence transmitted. A repeated sample beyond its window is the signature, and it is invisible from every other vantage point.

And if the artefacts are clicks and gaps rather than distortion? Then it is the opposite failure — genuine underruns — and the underrun flag from §5 answers the question §6's Z3 identified: whether the gaps originate in the device or somewhere past it.

The signature to keep: a perfect trace and an imperfect stream means the defect is in data that is correct but not current — and no bus-level tool can see it, because from the bus's point of view nothing went wrong.

10. Common Misconceptions

11. Reason It Through

A USB microphone and a USB speaker are on the same host, and audio is being passed from one to the other. Both work perfectly in isolation. Together, the audio slowly develops a periodic click — roughly once every few seconds — that gets no worse and no better.

What does periodic and stable tell you? That something is accumulating at a constant rate and being discharged at a fixed threshold. That is a drift signature, not a defect signature — defects do not usually keep such good time.

What could be drifting? The microphone's sample clock and the speaker's sample clock are independent crystals. Neither is wrong; they simply are not the same. If the microphone produces 48,000 samples per second by its own crystal and the speaker consumes 48,000 by its own, the difference between the two crystals is a slow, relentless accumulation.

Which direction produces which symptom? If the source is slightly faster, samples accumulate until something must be dropped. If slightly slower, the consumer runs dry and something must be inserted. Either way the correction is periodic, and its period is set by the buffer depth divided by the drift rate.

Is this a bug in either device? No — and this is the part that matters. Both are behaving exactly as specified. The mismatch is a property of the pair, and it exists in every system that connects two independently-clocked streams.

So what does USB provide? §3's answer: the endpoint's synchronisation type and the feedback endpoint. An asynchronous endpoint reports its true rate so the other end can resample; an adaptive one adjusts to the rate it is given. The protocol has a whole endpoint usage type for thisUSB_ENDPOINT_USAGE_FEEDBACK — which is the measure of how fundamental the problem is.

And how does §5's block relate? It does not solve this. A freshness model refuses to send stale data; it has nothing to say about two clocks that disagree by a few parts per million. MAX_AGE bounds how late a sample may be, and drift is not lateness — it is a rate mismatch that a bound cannot fix, only bound.

The transferable point: isochronous streams have two independent failure modes that are easy to confuse. Lateness is a device-side obligation solved by freshness logic. Drift is a system property solved by feedback and resampling. A design that handles one and calls it done will produce exactly the symptom above — and will look correct on every bench that tests a single device.

12. The Four Types Side by Side

Module 10 began with Chapter 10.1 deriving the four types from two orthogonal questions. Having built one block per type, the derivation can be filled in with what each type actually commits to.

ControlBulkInterruptIsochronous
bmAttributes[1:0]0231
Periodic?nonoyesyes
Reserves capacityprotected sharenoneyes, smallyes, committed
Declares a service intervalnonoyesyes
Retry on erroryesyesyesno mechanism
Per-transaction handshakeyesyesyesnone
Delivery guaranteedyesyesyesno
Latency boundednonoyes, ≈ one intervalyes, the slot
Typical throughputlowhighestlowhigh
Required of every deviceyes — EP0nonono
What failure looks likean error you are told aboutslowera missed deadlinea gap, or a wrong sample

Four observations that only appear once the table is assembled:

1. Bulk and Isochronous are opposites on every row that matters. Bulk reserves nothing, guarantees nothing about time, and guarantees delivery absolutely. Isochronous reserves everything, guarantees time absolutely, and guarantees no delivery at all. They are the two extreme answers to the same question, which is why Chapter 10.1 derived them from the same axis.

2. Only one row has a yes in a single column. Control is the only type every device must implement, because it is the only one that can exist before anything has been agreed — Chapter 10.2's bootstrap argument, visible here as a structural fact.

3. The retry row and the handshake row agree, and that is not a coincidence. A handshake exists to make retry possible. Remove the need to retry and the handshake becomes pure cost — which is why isochronous has neither and why §2's 283 ns saving is real.

4. The failure row is the most useful one to memorise. Each type fails in a characteristically different way, and the difference is diagnostic: a control failure tells you about itself; a bulk failure is slow; an interrupt failure misses a deadline; an isochronous failure is either a gap or something that looks entirely fine and is not. §9 is what the last of those costs.

13. Understanding Check

14. Summary

Isochronous is the only type with no retry, and the only one for which that is an advantage. For a stream with a deadline, a retry spends bandwidth to deliver data for a moment that has passed, and risks the next packet doing it. The meaningful failure is not corruption, it is lateness.

Which introduces freshness — the axis the other three types never had. Control, Bulk and Interrupt treat data as valuable indefinitely. An isochronous sample expires, and after expiry it is not merely stale, it is wrong data that looks right.

The trade is stated in one line: the bandwidth is guaranteed; the bytes are not. The reservation commits capacity in every interval; it commits nothing about delivery.

The no-handshake saving is real and smaller than its reputation — a fixed 283 ns per high-speed transaction, which is 13.0% of a 64-byte transaction and 1.4% of a 1024-byte one, the size isochronous endpoints actually use. The reason to choose isochronous is the deadline semantics, not the bandwidth.

The device's obligation follows directly: have current data ready, and when you do not, send nothing rather than something old. §6 measured the alternative — removing the age gate converted 32 visible gaps into 32 invisible wrong answers, raised the delivery count, and would be scored an improvement by any throughput metric. Freezing the age counter produced the identical failure by a different route, which is the argument for asserting on what is presented rather than on how age is computed.

And the module's sharpest verification lesson is here. Chapter 10.4's conservation law was the strongest check in that bench. The equivalent law here passes on every mutation, including both that ship stale data — because a conservation law counts events and cannot tell you an event was the wrong one. Conservation catches loss; it is blind to substitution. Where the failure mode is wrong value rather than missing value, the bench needs an independent reference for the value, and no amount of counting will substitute for it.

§12 assembles the four types side by side, and three facts only appear once the table exists: Bulk and Isochronous are opposites on every row that matters; Control is the only type every device must implement, which is its bootstrap role showing up as a structural fact; and the retry row and the handshake row agree, because a handshake exists to make retry possible and is pure cost without it.

15. What Comes Next

Module 10 has described four service contracts. Every one of them was expressed in terms of transfers, opportunities and obligations — and not one of them has yet touched a wire.

Module 11 is Packets, and it goes a level down: the token, data, handshake and start-of-frame packets that every transfer in this module is actually built from. The handshake packet this chapter said isochronous does not use is Chapter 11.3; the nothing new answer Chapter 10.4 kept describing in the abstract becomes a NAK with a bit pattern; and the frame boundary this chapter called a slot tick becomes Chapter 11.4's SOF packet with a frame number in it.

Everything Module 10 treated as a service becomes, in Module 11, a sequence of bits with a CRC over 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.