USB · Module 17
Transaction Timing on the Bus
Fitting the budget and fitting the time remaining are different questions. The end-of-frame guard band verified exhaustively — and the mutation that was unreachable until stimulus was built for it.
Three questions are answered. Chapter 17.3 chose who. Chapter 17.4 checked whether the work fits the frame's allocation. 17.1 and 17.2 built the opportunities.
One is left, and it is physical rather than arithmetic.
A transaction that fits the budget may still not fit the time remaining. And the difference is not a subtlety — it is the difference between a scheduler that works and one that produces bus errors under load.
1. Budget and Time Are Different Questions
Budget is an allocation. Chapter 17.4 tracks a quantity of frame time reserved for a class of work, refreshed each frame and consumed as transactions are committed. It answers has this class used up its share?
Placement is physical. It answers is there enough of the frame left, right now, for this transaction to finish before the boundary?
These come apart constantly:
| Situation | Budget says | Time says |
|---|---|---|
| early in a frame, budget nearly spent | no | plenty of time |
| late in a frame, budget barely touched | yes | not enough time |
| a long transaction, ample budget, 2 µs left | yes | no |
The second and third rows are why this chapter exists. A scheduler that checks only the budget will start a transaction with microseconds of frame remaining and hundreds of microseconds of work to do — and the transaction will still be on the wire when the frame ends.
2. What Happens If It Does Not Finish
A transaction still running when the frame boundary arrives is not merely late. It is on the bus during the interval the host needs for the next frame's SOF, and the condition has a name: babble.
The host's response is not to wait. The bus is a shared resource on a fixed schedule; a device still transmitting when its time is over is preventing every other device from being served. A host that tolerated it would have no schedule at all.
Lateness in 15.4 was a quality-of-service problem. Lateness here is a structural one — it does not degrade the stream, it corrupts the frame that follows.
So the host does not start transactions it cannot finish, and the mechanism is a guard band: a stretch at the end of every frame during which no new transaction may begin, sized so that anything started before it has time to complete.
3. The Guard Band, and What Refusal Means
|<---------------- frame ---------------->|
|<------- startable ------->|<-- guard -->|
0 LAST_START FRAME_LENA transaction may start only if it finishes at or before LAST_START. The test is inclusive — a transaction ending exactly at the last legal instant is legal — the same boundary convention 16.4 and 17.4 use, and for the same reason: the limit is what may be used, not what must be left over.
A transaction refused by the guard band is deferred, not dropped. It stays pending and is reconsidered at the next opportunity — Chapter 17.3 §5's retention invariant seen from the other end. The work did not fail; it did not fit here.
4. The Hardware, Before Any Language
State retained: the position within the frame, the time remaining in the transaction currently in flight, a busy flag, and a sticky babble flag.
On each bus clock: the position advances.
The placement test, combinational: position + length ≤ LAST_START, computed one bit wider than either operand. §12 is the account of what happens when it is not.
While busy: nothing else may start, and neither start_ok nor deferred asserts — there is no candidate being refused, there is simply no opportunity.
On a frame boundary: the position restarts. And if a transaction is still in flight, that is babble: it is abandoned, and the flag is set and stays set.
The boundary outranks everything, because a frame boundary is not negotiable.
5. Verilog
The RTL contract
- What it models: the end-of-frame placement decision, and detection of a transaction that outlives its frame.
- Why it exists: because fitting the budget and fitting the remaining time are different questions (§1), and the second failure is structural (§2).
- Inputs:
frame_tick,tick(one clock of bus time),txn_valid+txn_len,txn_start,bus_reset. - Authoritative state:
pos_r,left_r,busy_r,babble_r. - Derived state:
end_pos,fits,start_ok,deferred— all combinational. - Outputs:
start_ok,deferred,busy,frame_pos,babble. - Hardware implied: two
POS_Wcounters, one wide comparator, two flags. - Reset: asynchronous active-low
rst_n;bus_resetsynchronous and equivalent; both clear position, flight and babble. - Priority: the frame boundary outranks the tick, which outranks the start.
- Latency:
start_okanddeferredare combinational; state updates on the next edge. - Boundary behaviour: the test is inclusive at
LAST_START; the sum is computed one bit wider than its operands. - Collision behaviour: a frame boundary with a transaction in flight is babble — abandoned and reported, never silently truncated.
- Assumptions: one transaction at a time;
txn_lenis its duration in bus clocks;tickmarks bus time, which may be slower thanclk. - Omissions: no duration computation, no packet types, no recovery sequence, no per-endpoint state.
- What DV should verify: that the boundary is inclusive; that a refusal asserts
deferredand starts nothing; that nothing starts while busy; that a transaction outliving its frame sets babble; and that an absurdly long duration cannot wrap into appearing to fit.
// usb_txn_placer -- may this transaction START now?
//
// Chapter 17.3 chose WHO should be serviced. Chapter 17.4 checked whether the
// work fits the frame's BUDGET. Neither asks the question this module asks,
// and it is a different question:
//
// is there enough TIME LEFT IN THE FRAME to finish?
//
// Budget is an allocation -- a quantity of frame time reserved for a class of
// work. Placement is physical: a transaction started too close to the frame
// boundary is still running when the boundary arrives, and a transaction that
// runs past its frame is a babble condition rather than a late one.
//
// So the host keeps a GUARD BAND at the end of every frame and refuses to
// start anything that would not finish inside it. A refused transaction is
// DEFERRED, not dropped -- it stays pending and is reconsidered at the next
// opportunity, which is Chapter 17.3's retention invariant seen from the
// other end.
module usb_txn_placer #(
parameter integer POS_W = 16,
parameter integer FRAME_LEN = 48000, // controller clocks in a frame
parameter integer GUARD = 500 // clocks reserved at the frame end
) (
input wire clk,
input wire rst_n,
input wire bus_reset,
input wire frame_tick, // a new frame begins
input wire tick, // one controller clock of bus time
// ---- the candidate transaction ----
input wire txn_valid, // a transaction wants to start
input wire [POS_W-1:0] txn_len, // its estimated duration
output wire start_ok, // it fits before the guard band
output wire deferred, // it does NOT -- and is NOT dropped
// ---- the transaction in flight ----
input wire txn_start, // it actually started
output wire busy,
output wire [POS_W-1:0] frame_pos,
output wire babble // sticky: ran past the frame end
);
localparam [POS_W-1:0] LAST_START = FRAME_LEN - GUARD;
reg [POS_W-1:0] pos_r;
reg [POS_W-1:0] left_r; // clocks remaining in the transaction in flight
reg busy_r, babble_r;
assign frame_pos = pos_r;
assign busy = busy_r;
assign babble = babble_r;
// THE PLACEMENT TEST. Written with the addition INSIDE the comparison and
// at full width: computing `pos + len` into a POS_W register first would
// let a long transaction wrap and appear to fit. The comparison is
// INCLUSIVE -- a transaction that finishes exactly at the last legal
// instant is legal, the same boundary convention as Chapters 16.4 and 17.4.
wire [POS_W:0] end_pos = {1'b0, pos_r} + {1'b0, txn_len};
wire fits = (end_pos <= {1'b0, LAST_START});
assign start_ok = txn_valid && !busy_r && fits;
// Deferred is an OUTPUT, not an absence. A scheduler that cannot tell
// "nothing wanted to start" from "something wanted to and could not" cannot
// explain its own behaviour -- section 16's debugging depends on it.
assign deferred = txn_valid && !busy_r && !fits;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pos_r <= {POS_W{1'b0}}; left_r <= {POS_W{1'b0}};
busy_r <= 1'b0; babble_r <= 1'b0;
end else if (bus_reset) begin
pos_r <= {POS_W{1'b0}}; left_r <= {POS_W{1'b0}};
busy_r <= 1'b0; babble_r <= 1'b0;
end else begin
// The frame boundary outranks everything. A transaction still in
// flight when it arrives has run past its frame: that is BABBLE, and
// it is reported rather than quietly truncated.
if (frame_tick) begin
pos_r <= {POS_W{1'b0}};
if (busy_r) begin
babble_r <= 1'b1;
busy_r <= 1'b0;
left_r <= {POS_W{1'b0}};
end
end else begin
if (tick) pos_r <= pos_r + {{(POS_W-1){1'b0}}, 1'b1};
if (txn_start && start_ok) begin
busy_r <= 1'b1;
left_r <= txn_len;
end else if (busy_r && tick) begin
if (left_r <= {{(POS_W-1){1'b0}}, 1'b1}) begin
busy_r <= 1'b0;
left_r <= {POS_W{1'b0}};
end else begin
left_r <= left_r - {{(POS_W-1){1'b0}}, 1'b1};
end
end
end
end
end
endmoduleTwo details are worth naming.
end_pos is POS_W+1 bits and the addition is inside the comparison. Computing pos + len into a POS_W value first lets a long transaction wrap and appear to fit — and §12 is the account of that mutation being unreachable until stimulus was built for it.
deferred is a separate output rather than !start_ok. They differ when there is no candidate at all, and §3 is why that difference matters.
6. SystemVerilog
package usb_place_pkg;
// What the placer decided about this candidate. Three outcomes, named --
// because "nothing wanted to start" and "something wanted to and could
// not" are different facts, and a scheduler that cannot distinguish them
// cannot explain its own behaviour.
typedef enum logic [1:0] {
P_NONE, // no candidate this cycle
P_BUSY, // a transaction is already in flight
P_START, // it fits before the guard band
P_DEFER // it does not -- deferred, NOT dropped
} place_e;
endpackage
module usb_txn_placer_sv
import usb_place_pkg::*;
#(
parameter int unsigned POS_W = 16,
parameter int unsigned FRAME_LEN = 48000,
parameter int unsigned GUARD = 500
) (
input logic clk,
input logic rst_n,
input logic bus_reset,
input logic frame_tick,
input logic tick,
input logic txn_valid,
input logic [POS_W-1:0] txn_len,
output logic start_ok,
output logic deferred,
output place_e decision,
input logic txn_start,
output logic busy,
output logic [POS_W-1:0] frame_pos,
output logic babble
);
initial begin
if (GUARD >= FRAME_LEN)
$fatal(1, "GUARD=%0d leaves no usable frame (FRAME_LEN=%0d)",
GUARD, FRAME_LEN);
if ((FRAME_LEN - 1) >= (1 << POS_W))
$fatal(1, "POS_W=%0d cannot represent a frame of %0d clocks",
POS_W, FRAME_LEN);
end
localparam logic [POS_W-1:0] LAST_START = POS_W'(FRAME_LEN - GUARD);
logic [POS_W-1:0] left_r;
// THE PLACEMENT TEST. The addition is INSIDE the comparison and one bit
// wider than either operand: computing `pos + len` into a POS_W value
// first would let a long transaction wrap and appear to fit. The
// comparison is INCLUSIVE, matching Chapters 16.4 and 17.4.
wire [POS_W:0] end_pos = {1'b0, frame_pos} + {1'b0, txn_len};
wire fits = (end_pos <= {1'b0, LAST_START});
always_comb begin
if (!txn_valid) decision = P_NONE;
else if (busy) decision = P_BUSY;
else if (fits) decision = P_START;
else decision = P_DEFER;
end
assign start_ok = (decision == P_START);
assign deferred = (decision == P_DEFER);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || bus_reset) begin
frame_pos <= '0; left_r <= '0; busy <= 1'b0; babble <= 1'b0;
end else begin
// The frame boundary outranks everything. A transaction still in
// flight when it arrives has run past its frame: BABBLE, reported
// rather than quietly truncated.
if (frame_tick) begin
frame_pos <= '0;
if (busy) begin
babble <= 1'b1;
busy <= 1'b0;
left_r <= '0;
end
end else begin
if (tick) frame_pos <= frame_pos + 1'b1;
if (txn_start && start_ok) begin
busy <= 1'b1;
left_r <= txn_len;
end else if (busy && tick) begin
if (left_r <= POS_W'(1)) begin
busy <= 1'b0;
left_r <= '0;
end else begin
left_r <= left_r - 1'b1;
end
end
end
end
end
endmoduleplace_e names the four outcomes, and P_BUSY is the one that a two-signal interface cannot express: a candidate existed, and there was no opportunity to consider it. start_ok and deferred are then derived from the enum rather than being two independently computed conditions that must agree.
7. VHDL
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb_place_pkg is
-- What the placer decided about this candidate. "Nothing wanted to start"
-- and "something wanted to and could not" are different facts, and a
-- scheduler that cannot distinguish them cannot explain itself.
type place_t is (P_NONE, P_BUSY, P_START, P_DEFER);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_place_pkg.all;
entity usb_txn_placer_vhdl is
generic (
POS_W : positive := 16;
FRAME_LEN : positive := 48000;
GUARD : positive := 500
);
port (
clk : in std_logic;
rst_n : in std_logic;
bus_reset : in std_logic;
frame_tick : in std_logic;
tick : in std_logic;
txn_valid : in std_logic;
txn_len : in unsigned(POS_W-1 downto 0);
start_ok : out std_logic;
deferred : out std_logic;
decision : out place_t;
txn_start : in std_logic;
busy : out std_logic;
frame_pos : out unsigned(POS_W-1 downto 0);
babble : out std_logic
);
end entity;
architecture rtl of usb_txn_placer_vhdl is
constant LAST_START : unsigned(POS_W-1 downto 0) :=
to_unsigned(FRAME_LEN - GUARD, POS_W);
signal pos_r : unsigned(POS_W-1 downto 0) := (others => '0');
signal left_r : unsigned(POS_W-1 downto 0) := (others => '0');
signal busy_r : std_logic := '0';
signal babble_r : std_logic := '0';
signal end_pos : unsigned(POS_W downto 0);
signal fits : boolean;
signal dec : place_t;
begin
assert GUARD < FRAME_LEN
report "GUARD leaves no usable frame" severity failure;
assert FRAME_LEN - 1 < 2**POS_W
report "POS_W cannot represent a frame of this many clocks" severity failure;
frame_pos <= pos_r;
busy <= busy_r;
babble <= babble_r;
decision <= dec;
-- THE PLACEMENT TEST. resize() widens both operands before the add, so a
-- long transaction cannot wrap into looking as if it fits. The comparison
-- is INCLUSIVE, matching Chapters 16.4 and 17.4.
end_pos <= resize(pos_r, POS_W+1) + resize(txn_len, POS_W+1);
fits <= end_pos <= resize(LAST_START, POS_W+1);
dec <= P_NONE when txn_valid = '0' else
P_BUSY when busy_r = '1' else
P_START when fits else
P_DEFER;
start_ok <= '1' when dec = P_START else '0';
deferred <= '1' when dec = P_DEFER else '0';
process (clk, rst_n)
begin
if rst_n = '0' then
pos_r <= (others => '0'); left_r <= (others => '0');
busy_r <= '0'; babble_r <= '0';
elsif rising_edge(clk) then
if bus_reset = '1' then
pos_r <= (others => '0'); left_r <= (others => '0');
busy_r <= '0'; babble_r <= '0';
else
-- The frame boundary outranks everything. A transaction still in
-- flight when it arrives has run past its frame: BABBLE, reported
-- rather than quietly truncated.
if frame_tick = '1' then
pos_r <= (others => '0');
if busy_r = '1' then
babble_r <= '1';
busy_r <= '0';
left_r <= (others => '0');
end if;
else
if tick = '1' then
pos_r <= pos_r + 1;
end if;
if txn_start = '1' and dec = P_START then
busy_r <= '1';
left_r <= txn_len;
elsif busy_r = '1' and tick = '1' then
if left_r <= to_unsigned(1, POS_W) then
busy_r <= '0';
left_r <= (others => '0');
else
left_r <= left_r - 1;
end if;
end if;
end if;
end if;
end if;
end process;
end architecture;resize before the add, on both operands. The width extension is a property of the function rather than of the author's concatenation, which is the same guarantee 16.1 and 17.4 relied on.
place_t has no numeric encoding, so the four outcomes cannot be compared against integers.
8. Comparing the Three
| Concern | Verilog | SystemVerilog | VHDL |
|---|---|---|---|
| The four outcomes | two independent wires | place_e — derived from one enum | place_t, no encoding |
| Width extension | explicit concatenation | explicit concatenation | resize, defined for the type |
| Illegal parameterisation | undetected | two $fatal guards | two assert ... severity failure |
9. The Testbenches
The model computes the fit as a subtraction against the headroom where the design computes an addition against the limit — different arithmetic, so a width or wrap defect cannot be reproduced.
The placement decision is verified exhaustively. Every position in the frame crossed with every transaction length:
exhaustive placement sweep: 10201 of 10201 points verified101 positions × 101 lengths. For each, both start_ok and deferred are checked, so the complement is verified too — a design that asserted neither, or both, fails.
The directed sequence covers §3's boundary and §2's failure:
| Scenario | What it pins down |
|---|---|
| a short transaction at position 0 | fits |
one ending exactly at LAST_START | accepted — inclusive |
| one clock longer | deferred, and nothing starts |
| position walked to 50 | the headroom shrinks with it |
| a transaction in flight | nothing else starts, and nothing is deferred |
| a frame boundary while busy | babble, abandoned, position restarts |
| the next frame | babble is sticky |
| an absurdly long duration | deferred, not wrapped into fitting — see §12 |
| 101 × 101 positions and lengths | exhaustive |
| 6000 randomised cycles | five independent draws each |
REACH: start_ok=4418 deferred=6255 babble=65 exact-fits=95 sweep=1020110. Mutation Testing — Across All Three Languages
| ID | Mutation | Verilog | SystemVerilog | VHDL | Killed |
|---|---|---|---|---|---|
| — | baseline, no mutation | 0 | 0 | 0 | — |
| T1 | the guard band is removed | 4084 | 4084 | 2600 | ✅ all three |
| T2 | the fit boundary is exclusive | 376 | 376 | 198 | ✅ all three |
| T3 | the sum is computed at POS_W and wraps | 4 | 4 | 2 | ✅ all three — see §12 |
| T4 | babble is never reported | 5896 | 5896 | 5921 | ✅ all three |
| T5 | the position is never restarted at a boundary | 12359 | 12359 | 11931 | ✅ all three |
| T6 | deferral is not reported | 12274 | 12274 | 6368 | ✅ all three |
T5 and T6 are the largest. A position that never restarts makes every later placement decision wrong; a deferral that is never reported makes §3's distinction unavailable at every refusal.
T6's VHDL count is about half the other two — 6368 against 12 274. The designs are equivalent and the benches differ: the VHDL bench's randomised phase uses uniform with a different distribution of lengths, so it produces fewer refusals to observe. A difference in stimulus, not in semantics, and worth stating rather than leaving as an unexplained asymmetry.
11. The Mutation That Was Unreachable
T3 survived in all three languages — 0, 0, 0 — on the first full matrix.
It computes the placement sum at POS_W instead of POS_W+1, so a sufficiently long transaction wraps and appears to fit. §5 names it as the reason the addition is widened.
The first question is whether the mutation is equivalent, and it is not. The arithmetic is unambiguous:
correct (wide add): 50 + 65500 = 65550 -> DEFER
MUT T3 (POS_W add): (50 + 65500) mod 2^16 = 14 -> FITS (wrong!)The second question is whether the input is reachable, and it is — txn_len is a POS_W-bit input and 65500 is a legal value for it.
The third question is why nothing produced it, and the answer is that nothing came close. The frame in the testbench is 100 clocks; the longest length any test offered was 120. Against a 16-bit sum, 120 is not remotely enough to wrap. The defect was real, the input was legal, and the stimulus lived in a corner of the domain four hundred times too small to reach it.
Adding one directed test — walk to position 50, offer a length of 65500 — killed it in all three languages.
12. The Waveform
Accepted at the start of the frame, deferred near its end
12 cyclesThis waveform is in the controller clock domain. tick marks bus time and frame_pos counts it; nothing here is a shape on D+/D−. Reading it as bus signalling would suggest a frame is twelve clocks long, which is the conflation 17.1 §3 exists to prevent.
Cycle 7 is the chapter. A perfectly legitimate transaction, with budget available and an arbiter willing to grant it, refused because there is not enough frame left — and the refusal is announced rather than silent.
13. Assertions
// A1. SAFETY, §2's whole purpose: a transaction never starts if it cannot
// finish before the guard band.
property p_never_starts_too_late;
@(posedge clk) disable iff (!rst_n || bus_reset)
start_ok |-> ((frame_pos + txn_len) <= LAST_START);
endproperty
a_never_starts_too_late: assert property (p_never_starts_too_late);
// A2. SAFETY: start_ok and deferred are mutually exclusive, and exactly
// one of them holds when a candidate exists and the placer is free.
property p_decision_exclusive;
@(posedge clk) disable iff (!rst_n)
$onehot0({start_ok, deferred}) &&
((txn_valid && !busy) |-> (start_ok ^ deferred));
endproperty
a_decision_exclusive: assert property (p_decision_exclusive);
// A3. SAFETY: nothing starts while a transaction is in flight.
property p_no_overlap;
@(posedge clk) disable iff (!rst_n || bus_reset)
busy |-> !start_ok;
endproperty
a_no_overlap: assert property (p_no_overlap);
// A4. SAFETY: babble is sticky -- an over-running transaction is a fact
// about the configuration, not about the instant.
property p_babble_sticky;
@(posedge clk) disable iff (!rst_n || bus_reset)
$fell(babble) |-> 1'b0;
endproperty
a_babble_sticky: assert property (p_babble_sticky);
// A5. SAFETY: a frame boundary always restarts the position.
property p_boundary_restarts;
@(posedge clk) disable iff (!rst_n || bus_reset)
frame_tick |=> (frame_pos == '0);
endproperty
a_boundary_restarts: assert property (p_boundary_restarts);
// A6. PROGRESS, under explicit assumptions: a candidate short enough to
// fit an empty frame is eventually started. ASSUMES it stays offered,
// the placer becomes free, and frames keep arriving.
property p_short_txn_eventually_starts;
@(posedge clk) disable iff (!rst_n || bus_reset)
(txn_valid && (txn_len <= LAST_START)) |-> ##[1:(2*FRAME_LEN)] start_ok;
endproperty
a_short_txn_eventually_starts: assert property (p_short_txn_eventually_starts);Assertion contracts
| Claim | Safety / progress | Vacuity risk | Non-vacuity, and the assumptions | |
|---|---|---|---|---|
| A1 | nothing starts too late | safety | high — a placer that never starts anything passes | 4418 starts measured; paired with A6 |
| A2 | the decision is exclusive | safety | none for the $onehot0 half | holds every cycle |
| A3 | no overlapping transactions | safety | moderate — needs busy cycles | in flight throughout the run |
| A4 | babble is sticky | safety | high — needs babble to occur | 65 babble events |
| A5 | a boundary restarts the position | safety | moderate | boundaries throughout |
| A6 | a fitting candidate eventually starts | progress | high | assumes: the candidate stays offered, the placer becomes free, frames keep arriving |
A1 is the assertion that looks like the whole specification and is half of it. §47's argument once more: a placer that refuses everything satisfies A1, A2, A3, A4 and A5 perfectly — it never starts too late because it never starts, never overlaps because it is never busy, and keeps a sticky flag sticky by never setting it.
A6 is the only property that fails for it, and it is the only one needing its assumptions written down. Every transaction eventually starts is false and should be: one longer than LAST_START never fits any frame and must be refused for ever. The bounded form — a candidate short enough to fit an empty frame, kept offered, starts within two frames — is what the guard band actually guarantees.
14. Debugging: the Bus That Errors Only Under Load
A controller works correctly with one device. With several active devices it begins reporting bus errors — babble, or a device failing to respond — a few times a second. The errors correlate with load and never occur on an idle bus. The devices are fine on another host.
"Only under load" and "babble" together point at placement, and §1 says why: a lightly loaded frame has time left everywhere, so a missing guard band never matters. Load is what pushes transactions toward the end of the frame, which is exactly where the check that was skipped would have mattered.
The chain:
Where in the frame do the failures occur? If babble events cluster near the frame boundary, the guard band is absent or too small. If they are uniformly distributed, the fault is not placement — it is a device or a duration estimate.
Is there a guard band at all? Mutation T1 removes it entirely, and the signature is precisely this: correct at low load, failing near boundaries as load rises. Compare the last legal start against the frame length; if they are equal, there is no guard.
Are durations estimated correctly? A guard band sized for a short transaction does not protect a long one. If placement refuses correctly but babble still occurs, the transaction is running longer than its txn_len claimed — which is 17.4's estimate-versus-actual problem arriving here as a timing failure rather than an accounting one.
Is deferred ever asserted? §3. A scheduler that never defers under load is not checking, and the absence of that signal is more informative than the presence of the errors.
The first divergence. Compare the bench's independent position and headroom against the design's at each opportunity. The first opportunity where a start was permitted and the model says it should not have been is the bug.
15. Common Misconceptions
"If it fits the budget it can be scheduled." Budget is an allocation; placement is time remaining (§1). They come apart constantly.
"A transaction that overruns is just late." It is on the bus when the next frame needs it — babble, a structural failure rather than a quality one (§2).
"The host can wait for it to finish." The schedule is fixed. Waiting means every other device misses its turn (§2).
"A refused transaction has failed." It is deferred and still pending (§3) — 17.3's retention invariant from the other end.
"deferred is just !start_ok." They differ when there is no candidate, and §3 explains why that difference is the whole debugging story.
"The guard band wastes bandwidth." It costs the tail of each frame; without it a transaction overrunning costs the next frame (§2).
"Widening the sum by one bit is defensive over-engineering." §11: mutation T3 makes a 65500-clock transaction appear to fit in a 100-clock frame.
"Exhaustive testing of the decision means the block is verified." 10201 points verified the decision and T3 still survived (§11), because the exhaustive domain was the realistic one, not the legal one.
16. Exercises
1. A full-speed frame is 12 000 bit times and the longest transaction is 1023 bytes of payload. Using 16.4's bit-stuffing arithmetic, compute a guard band that protects it, and express it as a percentage of the frame.
2. §11's exhaustive sweep covered lengths 0–100 in a 100-clock frame and missed T3. Define the sweep you would run instead to cover the legal input domain rather than the realistic one, and say how many points it has.
3. Extend the design to two transactions in flight. Determine what busy becomes, whether deferred still has a single meaning, and what new collision appears.
4. Mutation T6 removes deferred. Write the debugging procedure for §14's scenario without that signal, and count the steps against the procedure in §14.
5. Write an SVA property that catches T3 — the wrapping sum — without referring to POS_W. State what it must reference instead.
6. §14 says failures appearing only under load usually indicate a check exercised only under load. Find another block in Module 17 with the same property, and say what stimulus would exercise it early.
17. Summary
Budget and time are different questions (§1). A transaction can fit the frame's allocation and still not fit the time remaining, and a scheduler that checks only the first will start work it cannot finish.
A transaction that outlives its frame is babble (§2) — on the bus when the next frame needs it. Lateness here corrupts the following frame rather than degrading a stream.
The guard band is the mechanism (§3), the test is inclusive, and a refusal is a deferral: the work stays pending. deferred is an output, because nothing wanted to start, something was outranked and something would not fit are three different facts that produce the same silence.
All three HDL implementations were simulated (§18) and six mutations died in all three (§10), with the placement decision verified exhaustively over 10 201 position-length pairs (§9).
And T3 survived that entire sweep (§11). It makes a long transaction wrap and appear to fit, and it was unreachable because the longest length any test offered was 120 against a 16-bit sum. Not equivalent, not unobservable — the stimulus explored the realistic domain rather than the legal one. One directed test killed it.
Three mutations have now survived a full matrix in this module, for three different reasons (§11): one genuinely equivalent, one a stimulus hole, one unreachable. "Survived" is the start of a diagnosis, not the end of one.
And the testbench lost a sign (§9) — -1 >= len reading as true because len was an unsigned reg — which is Chapter 15.3's RTL defect reappearing two modules later in verification code.
18. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_txn_placer | tp_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors | ✅ all six |
| SystemVerilog | usb_txn_placer_sv | tp_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors | ✅ all six |
| VHDL-2008 | usb_txn_placer_vhdl | tp_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors | ✅ all six |
| SVA (§13) | — | — | ❌ unsupported by Icarus | ❌ | — |
T6's VHDL count differs from the other two by roughly half (§10), and the cause is the VHDL bench's different random length distribution rather than any semantic difference. Stated rather than left as an unexplained asymmetry.
19. What Comes Next
Module 17 is complete. The host's schedule has been built from the ground up: a frame (17.1), eight microframes inside it (17.2), a decision about who is serviced at each opportunity (17.3), a ledger of what the frame can still afford (17.4), and a guard band that refuses work the frame has no time for.
Everything in it assumed a single, flat bus. Every device was directly reachable, every transaction ran at one speed, and a scheduling opportunity was an opportunity for any of them.
Module 18 — USB Hubs — removes that assumption. A hub is what makes USB a tree rather than a cable, and it introduces problems the flat model cannot express: ports that must be individually enabled, reset and suspended; devices that appear and disappear beneath a hub the host cannot see directly; a depth limit that exists for timing reasons rather than architectural ones; and — most consequentially for everything this module built — a full-speed device sitting behind a high-speed hub, whose transactions cannot simply be scheduled into a 125 µs microframe because the device cannot run at that speed.
That last problem has a name and a dedicated piece of hardware, and it is where Module 18 begins.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
Hub Architecture
A hub is three devices in one package, and its repeater is deliberately asymmetric: downstream is a broadcast, upstream is a select of exactly one — and two talkers connects neither.
- Related topic
Host-Side Scheduling
A USB transaction cannot be stopped once its token goes out, so the scheduler must ask whether it will finish before it starts — and the periodic reserve exists to protect bulk traffic, not to limit isochronous.
- Related topic
DMA Integration
A descriptor has a byte count and the wire has packets, and the rule that converts one to the other is not ceil(length / packet size) — the version that is hangs on exactly the buffer sizes everybody uses.
- Related topic
Host / Device / Hub Identification
Host and device take fifteen seconds; the hub is where the interview is decided — a hub is a repeater, not a switch, and no downstream port can ever reach another one.
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.
