Skip to content
VLSI Mentor

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:

SituationBudget saysTime says
early in a frame, budget nearly spentnoplenty of time
late in a frame, budget barely touchedyesnot enough time
a long transaction, ample budget, 2 µs leftyesno

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   |<---------------- frame ---------------->|
   |<------- startable ------->|<-- guard -->|
   0                      LAST_START     FRAME_LEN

A 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.

A sequence diagram showing a transaction passing through the host controller's scheduling stages. The arbiter of chapter seventeen point three selects a requester that is pending, enabled and within budget, producing a grant. The grant is offered to the placer together with the transaction's estimated duration. The placer compares the current position within the frame plus that duration against the last legal start position, which is the frame length less the guard band. If the transaction fits, the placer permits the start, the transaction is driven onto the bus, and on completion the budget ledger is settled at the actual cost. If instead the transaction does not fit, the placer asserts deferred rather than started: nothing is driven, the requester remains pending in the arbiter, and it is reconsidered at the next opportunity. The diagram also shows the failure case in which a transaction is still in flight when the frame boundary arrives, which is babble: the transaction is abandoned and a sticky flag is raised, because it was occupying the bus during the interval the next frame needs.From candidate to the wire — and the two refusalsArbiter (17.3)Budget (17.4)PlacerBuscan_fit — the budgetpermits itgrant + estimateddurationpos + len ≤LAST_START ?START — thetransaction isdrivencomplete → settle atthe ACTUAL cost…or DEFERRED: notime left this framethe requester staysPENDING — work isnot lostframe boundary whilein flight = BABBLE
Figure 1 — the four questions a transaction must pass, and the two ways it can be refused. Both refusals leave the work pending; neither is an error.

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_W counters, one wide comparator, two flags.
  • Reset: asynchronous active-low rst_n; bus_reset synchronous and equivalent; both clear position, flight and babble.
  • Priority: the frame boundary outranks the tick, which outranks the start.
  • Latency: start_ok and deferred are 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_len is its duration in bus clocks; tick marks bus time, which may be slower than clk.
  • 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 deferred and 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.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

Two 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

place_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

ConcernVerilogSystemVerilogVHDL
The four outcomestwo independent wiresplace_e — derived from one enumplace_t, no encoding
Width extensionexplicit concatenationexplicit concatenationresize, defined for the type
Illegal parameterisationundetectedtwo $fatal guardstwo 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  exhaustive placement sweep: 10201 of 10201 points verified

101 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:

ScenarioWhat it pins down
a short transaction at position 0fits
one ending exactly at LAST_STARTaccepted — inclusive
one clock longerdeferred, and nothing starts
position walked to 50the headroom shrinks with it
a transaction in flightnothing else starts, and nothing is deferred
a frame boundary while busybabble, abandoned, position restarts
the next framebabble is sticky
an absurdly long durationdeferred, not wrapped into fitting — see §12
101 × 101 positions and lengthsexhaustive
6000 randomised cyclesfive independent draws each
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  REACH: start_ok=4418 deferred=6255 babble=65 exact-fits=95 sweep=10201

10. Mutation Testing — Across All Three Languages

IDMutationVerilogSystemVerilogVHDLKilled
—baseline, no mutation000—
T1the guard band is removed408440842600✅ all three
T2the fit boundary is exclusive376376198✅ all three
T3the sum is computed at POS_W and wraps442✅ all three — see §12
T4babble is never reported589658965921✅ all three
T5the position is never restarted at a boundary123591235911931✅ all three
T6deferral is not reported12274122746368✅ 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 cycles
A waveform of the transaction placer over twelve bus clocks, using a deliberately shortened frame of twelve clocks with a guard band of three, so the last legal start position is nine. At cycle zero the position is zero and a candidate transaction of three clocks is offered; since zero plus three is at most nine, the start-is-permitted output asserts and the transaction begins. The busy output is high through cycles one, two and three while the transaction runs, and falls at cycle four when it completes. The position counter advances with each bus clock. At cycle seven the position has reached six and a candidate of five clocks is offered; six plus five is eleven, which exceeds the last legal start of nine, so the start output stays low and the deferred output asserts instead. Nothing begins, and the transaction remains pending for the next frame. Every signal here is in the controller clock domain and none of it depicts USB wire signalling.0 + 3 ≤ 9 — accepted0 + 3 ≤ 9 — acceptedin flightin flight6 + 5 = 11 > 9 — DEFERRED, not dropped6 + 5 = 11 > 9 — DEFERRED,not droppedcycle01234567891011frame_pos0012345678910txn_validtxn_len3——————5————start_okdeferredbusyt0t1t2t3t4t5t6t7t8t9t10t11
Figure 2 — twelve bus clocks of a deliberately tiny frame (12 clocks, 3 of guard), taken from the simulator. One transaction is accepted at position 0; a later, longer one is refused by the guard band and deferred.

This 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // 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

ClaimSafety / progressVacuity riskNon-vacuity, and the assumptions
A1nothing starts too latesafetyhigh — a placer that never starts anything passes4418 starts measured; paired with A6
A2the decision is exclusivesafetynone for the $onehot0 halfholds every cycle
A3no overlapping transactionssafetymoderate — needs busy cyclesin flight throughout the run
A4babble is stickysafetyhigh — needs babble to occur65 babble events
A5a boundary restarts the positionsafetymoderateboundaries throughout
A6a fitting candidate eventually startsprogresshighassumes: 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

LanguageDesignTestbenchAnalysed / compiledSimulatedMutations
Verilog-2005usb_txn_placertp_v_tb.v✅ Icarus -g2005✅ 0 errors✅ all six
SystemVerilogusb_txn_placer_svtp_sv_tb.sv✅ Icarus -g2012✅ 0 errors✅ all six
VHDL-2008usb_txn_placer_vhdltp_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

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.