Skip to content
VLSI Mentor

USB · Module 17

Microframe Scheduling (125 µs)

High speed divides every frame into eight 125 µs microframes — and the frame number does not advance eight times faster. Two nested moduli, and the mutation a range-constrained VHDL type caught that the others could not.

Chapter 17.1 built one time base: a 1 ms frame, numbered modulo 2048, its boundary derived by counting controller clocks. Every service requirement in Modules 14–16 was denominated in those frames.

High speed keeps that frame and puts eight scheduling opportunities inside it.

The obvious guess about what that means to the hardware is wrong, and it is wrong in a way that produces working silicon which fails subtly. The frame number does not advance eight times faster. It advances once per millisecond — exactly as in 17.1 — while a separate three-bit index walks beneath it.

This chapter builds that nested structure, and its central property is one sentence: for eight consecutive scheduling opportunities, the number the SOF carries is the same value.

1. Eight Per Millisecond

The relationship is stated directly in the Linux USB core, in the documentation of how interrupt endpoints express their intervals:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
 * Note that High Speed and SuperSpeed(+) interrupt endpoints use a logarithmic
 * encoding of the endpoint interval, and express polling intervals in
 * microframes (eight per millisecond) rather than in frames (one per
 * millisecond).

"Microframes (eight per millisecond) rather than in frames (one per millisecond)." That is the whole arithmetic: 1 ms ÷ 8 = 125 µs, and Chapter 15.4 already relied on it when it decoded bInterval as 2^(bInterval−1) microframes.

What changes for the scheduler is the rate of opportunity, not the numbering. Eight times as many moments at which work can be placed; the same frame number stamped on all eight.

Full speedHigh speed
Scheduling opportunityevery 1 msevery 125 µs
Opportunities per frame18
Frame number advancesonce per opportunityonce per eight
bInterval unitsframesmicroframes

2. The Number Is the Same Eight Times

This is the property that catches people, so it is worth stating it in the form a device actually experiences.

A high-speed device receives eight SOF packets per millisecond. All eight carry the same 11-bit frame number. The number changes on the ninth.

So the microframe index is not transmitted in that field. A device that needs to know which of the eight it is in counts SOFs between frame-number changes — the index is derived, not received.

3. Two Moduli, Nested — Not One Faster Counter

The structure follows directly, and the phrasing matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
controller clocks --> microframe (125 us) --> frame (1 ms)
                      index counts 0..7        number advances ONCE per 8

A frame boundary is not a separate event from a microframe boundary. It is the microframe boundary that rolls the index over — always also a microframe boundary, never instead of one. Deriving it that way rather than running a second independent counter is what keeps the two moduli from drifting apart, and it is why this chapter's RTL computes at_fbound from at_ubound rather than counting to 8000 separately.

Six time concepts are now in play. Chapter 17.1 §3 named four; this chapter adds two:

TermWhat it isWho defines it
controller clockthe RTL clockthe implementation
microframea 125 µs intervalUSB
framea 1 ms interval — eight microframesUSB
frame numberthe 11-bit value the SOF carriesUSB
microframe index0–7 within the frame, derived not receivedUSB structure, local state
frame counterwhat software reads backthe host controller

uframe_tick and frame_tick are both controller-domain pulses, and every waveform in this chapter is in that domain.

4. The Hardware, Before Any Language

State retained: the frame number, the microframe index, the position within the current microframe, and a pending SOF request.

On each controller clock while operating: the position advances.

When the position has run a whole microframe: the position restarts, uframe_tick pulses, and a SOF is requested — because a SOF is transmitted in every microframe, carrying the same number eight times over.

If the index is below 7: it advances, and that is all. This is not a frame boundary.

If the index is 7: it rolls to 0, frame_tick pulses in addition to uframe_tick, and the frame number advances — modulo 2048.

When the port is not operating: both protocol quantities are held — the number and the index, because a device is tracking both — and only the local position is parked.

On reset or bus reset: both restart at zero.

The missed-SOF report is per microframe. A boundary arriving with the previous SOF un-acked renews the request and reports the loss, exactly as in 17.1 §4 — but eight times as often, which is why the report matters more here.

A sequence diagram of one millisecond of high-speed USB as a device experiences it. The host's microframe timer reaches a boundary and requests a start-of-frame; the packet engine transmits a start-of-frame token carrying frame number four hundred and twelve, and the device receives it. The device increments its own microframe index from zero to one, because the index is not carried in the frame-number field and must be derived by counting packets. This repeats for the second through the eighth microframe: each time a start-of-frame token arrives carrying the same frame number four hundred and twelve, and each time the device advances its index. After the eighth, the timer's index rolls over from seven to zero, which is also the one-millisecond frame boundary, and only then does the frame number advance to four hundred and thirteen. The device observes the change in frame number and resets its own index to zero, re-synchronising the two ends. A device that tracked only the frame number would have been unable to distinguish any of the eight opportunities from one another.Eight SOFs, one number, and an index the device must deriveµframe timerPacket engineDeviceboundary 0 — sof_reqSOF, frame = 412index 0 → 1(counted, notreceived)boundaries 1…6 —sof_req ×6SOF ×6, frame = 412every timeindex 1 → 7index rolls 7 → 0:THIS is the 1 msboundarySOF, frame = 413number changed →reset index to 0
Figure 1 — one millisecond of high-speed bus, as the device experiences it. Eight SOF packets arrive carrying one frame number, and the device recovers the microframe index by counting them.

5. Verilog

The RTL contract

  • What it models: the high-speed time base — eight 125 µs microframes nested inside each 1 ms frame.
  • Why it exists: because the scheduling opportunity is now the microframe (§1) while the frame number still advances once per millisecond (§2).
  • Inputs: enable, sof_ack, bus_reset.
  • Authoritative state: frame_r and uf_r — both protocol quantities; pos_r — a local measurement.
  • Derived state: at_ubound, and at_fbound derived from it rather than counted separately.
  • Outputs: sof_frame_no, uframe, uframe_pos, uframe_tick, frame_tick, sof_req, frame_wrapped, sof_missed.
  • Hardware implied: one POS_W counter, one 3-bit counter, one 11-bit counter, two comparators, five flags.
  • Reset: asynchronous active-low rst_n; bus_reset synchronous and equivalent, and both restart the number and the index.
  • Priority: !enable outranks the boundary, which outranks the ordinary count. A frame boundary is a microframe boundary that also rolls the index.
  • Latency: one cycle; every output is registered.
  • Boundary behaviour: the index wraps at 8, the number at 2048 — two nested moduli.
  • Collision behaviour: a boundary with an un-acked SOF renews the request and pulses sof_missed, eight times per frame rather than once.
  • Assumptions: TICKS_PER_UFRAME controller clocks equal one 125 µs microframe.
  • Omissions: no PHY, no packet engine, no split transactions, no transaction translator — those are Module 18's.
  • What DV should verify: that the frame number is unchanged across eight consecutive microframe boundaries; that every index 0–7 is visited; that a frame boundary lands exactly where the index rolls to zero; that both quantities survive a disable and restart on a bus reset.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_uframe_timer -- the high-speed time base: eight 125 us microframes
// nested inside each 1 ms frame.
//
// The structure is two moduli, not one faster counter:
//
//   controller clocks --> microframe (125 us)  --> frame (1 ms)
//                         counts 0..7             advances ONCE per 8
//
// The subtlety that catches almost everyone: the frame NUMBER does not
// advance eight times faster at high speed. It advances once per
// millisecond, exactly as at full speed, while a three-bit index walks
// beneath it. So for EIGHT consecutive scheduling opportunities the number
// the SOF carries is the SAME value, and a device or controller that
// identifies a service opportunity by frame number alone has eight ways to
// be wrong.
//
// `sof_frame_no` is what the SOF token carries. `uframe` is the controller's
// own index and is NOT transmitted in that field -- a device tracks it by
// counting SOFs between frame-number changes.
module usb_uframe_timer #(
  parameter integer TICKS_PER_UFRAME = 6000,  // controller clocks per 125 us
  parameter integer FRAME_W          = 11,
  parameter integer POS_W            = 16
) (
  input  wire                clk,
  input  wire                rst_n,
  input  wire                bus_reset,
  input  wire                enable,
  input  wire                sof_ack,
  output wire [FRAME_W-1:0]  sof_frame_no,  // the value the SOF carries
  output wire [2:0]          uframe,        // 0..7, NOT on the wire
  output wire [POS_W-1:0]    uframe_pos,
  output wire                uframe_tick,   // every 125 us
  output wire                frame_tick,    // every 1 ms (uframe 7 -> 0)
  output wire                sof_req,
  output wire                frame_wrapped,
  output wire                sof_missed
);
  localparam [FRAME_W-1:0] FRAME_MAX = {FRAME_W{1'b1}};   // 2047
  localparam [2:0]         UF_MAX    = 3'd7;
  localparam [POS_W-1:0]   POS_LAST  = TICKS_PER_UFRAME - 1;

  reg [FRAME_W-1:0] frame_r;
  reg [2:0]         uf_r;
  reg [POS_W-1:0]   pos_r;
  reg               uftick_r, ftick_r, wrap_r, sofreq_r, missed_r;

  assign sof_frame_no  = frame_r;
  assign uframe        = uf_r;
  assign uframe_pos    = pos_r;
  assign uframe_tick   = uftick_r;
  assign frame_tick    = ftick_r;
  assign sof_req       = sofreq_r;
  assign frame_wrapped = wrap_r;
  assign sof_missed    = missed_r;

  wire at_ubound = enable && (pos_r == POS_LAST);
  // The FRAME boundary is the microframe boundary that rolls the index over.
  // It is a derived condition, not a second counter -- deriving it keeps the
  // two moduli from drifting apart, which they can if each is counted alone.
  wire at_fbound = at_ubound && (uf_r == UF_MAX);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      frame_r <= {FRAME_W{1'b0}}; uf_r <= 3'd0; pos_r <= {POS_W{1'b0}};
      uftick_r <= 1'b0; ftick_r <= 1'b0; wrap_r <= 1'b0;
      sofreq_r <= 1'b0; missed_r <= 1'b0;
    end else if (bus_reset) begin
      frame_r <= {FRAME_W{1'b0}}; uf_r <= 3'd0; pos_r <= {POS_W{1'b0}};
      uftick_r <= 1'b0; ftick_r <= 1'b0; wrap_r <= 1'b0;
      sofreq_r <= 1'b0; missed_r <= 1'b0;
    end else begin
      uftick_r <= 1'b0;
      ftick_r  <= 1'b0;
      wrap_r   <= 1'b0;
      missed_r <= 1'b0;

      if (sof_ack) sofreq_r <= 1'b0;

      if (!enable) begin
        // Hold BOTH protocol quantities -- the frame number and the
        // microframe index are each part of the schedule the device is
        // tracking. Park only the local position.
        pos_r <= {POS_W{1'b0}};
      end else if (at_ubound) begin
        pos_r    <= {POS_W{1'b0}};
        uftick_r <= 1'b1;

        // A SOF is transmitted in EVERY microframe, so the request is
        // renewed eight times per frame -- carrying the same number each
        // time. The missed-SOF report is per microframe accordingly.
        if (sofreq_r && !sof_ack) missed_r <= 1'b1;
        sofreq_r <= 1'b1;

        if (uf_r == UF_MAX) begin
          uf_r    <= 3'd0;
          ftick_r <= 1'b1;
          // The frame number advances HERE -- once per eight microframes.
          if (frame_r == FRAME_MAX) begin
            frame_r <= {FRAME_W{1'b0}};
            wrap_r  <= 1'b1;
          end else begin
            frame_r <= frame_r + {{(FRAME_W-1){1'b0}}, 1'b1};
          end
        end else begin
          uf_r <= uf_r + 3'd1;
        end
      end else begin
        pos_r <= pos_r + {{(POS_W-1){1'b0}}, 1'b1};
      end
    end
  end
endmodule

Two details are worth naming.

at_fbound is derived from at_ubound, not counted separately. A second counter running to 8000 controller clocks would be a second opinion about when a millisecond has elapsed, and two opinions drift. Deriving the frame boundary as the microframe boundary that rolls the index makes drift structurally impossible.

Both protocol quantities are held when the port is disabled. Chapter 17.1 held one; here there are two, and a device tracking its position within a frame needs both. Only pos_r — the local measurement — is parked.

6. SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package usb_uframe_pkg;
  // What this microframe boundary is. The three cases are NESTED moduli, not
  // three independent events, and naming them keeps that visible: a frame
  // boundary is always also a microframe boundary, never instead of one.
  typedef enum logic [1:0] {
    B_NONE,    // an ordinary controller clock inside the microframe
    B_UFRAME,  // a 125 us boundary that does NOT roll the index over
    B_FRAME    // the 125 us boundary that rolls 7 -> 0: also a 1 ms boundary
  } bnd_e;
endpackage

module usb_uframe_timer_sv
  import usb_uframe_pkg::*;
#(
  parameter int unsigned TICKS_PER_UFRAME = 6000,
  parameter int unsigned FRAME_W          = 11,
  parameter int unsigned POS_W            = 16
) (
  input  logic               clk,
  input  logic               rst_n,
  input  logic               bus_reset,
  input  logic               enable,
  input  logic               sof_ack,
  output logic [FRAME_W-1:0] sof_frame_no,
  output logic [2:0]         uframe,
  output logic [POS_W-1:0]   uframe_pos,
  output logic               uframe_tick,
  output logic               frame_tick,
  output logic               sof_req,
  output logic               frame_wrapped,
  output logic               sof_missed
);
  initial begin
    if (FRAME_W != 11)
      $fatal(1, "FRAME_W=%0d: the USB 2.0 SOF frame-number field is 11 bits",
             FRAME_W);
    if (TICKS_PER_UFRAME < 2)
      $fatal(1, "TICKS_PER_UFRAME=%0d leaves no room for a microframe",
             TICKS_PER_UFRAME);
    if ((TICKS_PER_UFRAME - 1) >= (1 << POS_W))
      $fatal(1, "POS_W=%0d cannot represent a microframe of %0d clocks",
             POS_W, TICKS_PER_UFRAME);
  end

  localparam logic [FRAME_W-1:0] FRAME_MAX = '1;                 // 2047
  localparam logic [2:0]         UF_MAX    = 3'd7;
  localparam logic [POS_W-1:0]   POS_LAST  = POS_W'(TICKS_PER_UFRAME - 1);

  bnd_e bnd;
  always_comb begin
    if      (!enable || (uframe_pos != POS_LAST)) bnd = B_NONE;
    else if (uframe == UF_MAX)                    bnd = B_FRAME;
    else                                          bnd = B_UFRAME;
  end

  // A microframe boundary is EITHER kind. Deriving it from the enum rather
  // than recomputing the comparison keeps the two from drifting apart.
  wire at_ubound = (bnd != B_NONE);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || bus_reset) begin
      sof_frame_no <= '0; uframe <= '0; uframe_pos <= '0;
      uframe_tick <= 1'b0; frame_tick <= 1'b0; frame_wrapped <= 1'b0;
      sof_req <= 1'b0; sof_missed <= 1'b0;
    end else begin
      uframe_tick   <= 1'b0;
      frame_tick    <= 1'b0;
      frame_wrapped <= 1'b0;
      sof_missed    <= 1'b0;

      if (sof_ack) sof_req <= 1'b0;

      if (!at_ubound) begin
        // Hold BOTH protocol quantities when disabled; park only the local
        // position. A disabled port must not resume mid-microframe.
        if (!enable) uframe_pos <= '0;
        else         uframe_pos <= uframe_pos + 1'b1;
      end else begin
        uframe_pos  <= '0;
        uframe_tick <= 1'b1;

        // A SOF is transmitted in EVERY microframe, carrying the same number
        // eight times over, so the request is renewed per microframe.
        if (sof_req && !sof_ack) sof_missed <= 1'b1;
        sof_req <= 1'b1;

        unique case (bnd)
          B_UFRAME: begin
            uframe <= uframe + 3'd1;
          end
          B_FRAME: begin
            uframe     <= '0;
            frame_tick <= 1'b1;
            // The frame number advances HERE -- once per eight microframes.
            if (sof_frame_no == FRAME_MAX) begin
              sof_frame_no  <= '0;
              frame_wrapped <= 1'b1;
            end else begin
              sof_frame_no <= sof_frame_no + 1'b1;
            end
          end
          B_NONE: ;   // unreachable here; stated so the case is exhaustive
        endcase
      end
    end
  end
endmodule

bnd_e names the nesting rather than describing it. B_UFRAME and B_FRAME are both microframe boundaries; the enumeration makes that a fact about the type rather than a comment, and at_ubound is then derived from the enum — (bnd != B_NONE) — instead of recomputing the comparison. Two expressions that must agree have become one.

7. VHDL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb_uframe_pkg is
  -- What this microframe boundary is. The three cases are NESTED moduli, not
  -- three independent events: a frame boundary is always also a microframe
  -- boundary, never instead of one.
  type bnd_t is (B_NONE, B_UFRAME, B_FRAME);
end package;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_uframe_pkg.all;

entity usb_uframe_timer_vhdl is
  generic (
    TICKS_PER_UFRAME : positive := 6000;
    FRAME_W          : positive := 11;
    POS_W            : positive := 16
  );
  port (
    clk           : in  std_logic;
    rst_n         : in  std_logic;
    bus_reset     : in  std_logic;
    enable        : in  std_logic;
    sof_ack       : in  std_logic;
    sof_frame_no  : out unsigned(FRAME_W-1 downto 0);
    uframe        : out unsigned(2 downto 0);
    uframe_pos    : out unsigned(POS_W-1 downto 0);
    uframe_tick   : out std_logic;
    frame_tick    : out std_logic;
    sof_req       : out std_logic;
    frame_wrapped : out std_logic;
    sof_missed    : out std_logic
  );
end entity;

architecture rtl of usb_uframe_timer_vhdl is
  constant FRAME_MAX : unsigned(FRAME_W-1 downto 0) := (others => '1');
  constant UF_MAX    : unsigned(2 downto 0) := to_unsigned(7, 3);
  constant POS_LAST  : unsigned(POS_W-1 downto 0) :=
    to_unsigned(TICKS_PER_UFRAME - 1, POS_W);

  signal frame_r  : unsigned(FRAME_W-1 downto 0) := (others => '0');
  -- The index is range-constrained: a value outside 0..7 is an error the
  -- simulator reports rather than a silent wrap. Chapter 16.5 measured what
  -- that buys when a mutation pushes a counter past its limit.
  signal uf_r     : integer range 0 to 7 := 0;
  signal pos_r    : unsigned(POS_W-1 downto 0) := (others => '0');
  signal uftick_r, ftick_r, wrap_r, sofreq_r, missed_r : std_logic := '0';

  signal bnd       : bnd_t;
  signal at_ubound : boolean;
begin
  assert FRAME_W = 11
    report "the USB 2.0 SOF frame-number field is 11 bits" severity failure;
  assert TICKS_PER_UFRAME >= 2
    report "TICKS_PER_UFRAME leaves no room for a microframe" severity failure;
  assert TICKS_PER_UFRAME - 1 < 2**POS_W
    report "POS_W cannot represent a microframe of this many clocks"
    severity failure;

  sof_frame_no  <= frame_r;
  uframe        <= to_unsigned(uf_r, 3);
  uframe_pos    <= pos_r;
  uframe_tick   <= uftick_r;
  frame_tick    <= ftick_r;
  sof_req       <= sofreq_r;
  frame_wrapped <= wrap_r;
  sof_missed    <= missed_r;

  bnd <= B_NONE   when enable = '0' or pos_r /= POS_LAST else
         B_FRAME  when uf_r = 7 else
         B_UFRAME;

  at_ubound <= bnd /= B_NONE;

  process (clk, rst_n)
  begin
    if rst_n = '0' then
      frame_r <= (others => '0'); uf_r <= 0; pos_r <= (others => '0');
      uftick_r <= '0'; ftick_r <= '0'; wrap_r <= '0';
      sofreq_r <= '0'; missed_r <= '0';
    elsif rising_edge(clk) then
      if bus_reset = '1' then
        frame_r <= (others => '0'); uf_r <= 0; pos_r <= (others => '0');
        uftick_r <= '0'; ftick_r <= '0'; wrap_r <= '0';
        sofreq_r <= '0'; missed_r <= '0';
      else
        uftick_r <= '0';
        ftick_r  <= '0';
        wrap_r   <= '0';
        missed_r <= '0';

        if sof_ack = '1' then
          sofreq_r <= '0';
        end if;

        if not at_ubound then
          -- Hold BOTH protocol quantities when disabled; park only the local
          -- position. A disabled port must not resume mid-microframe.
          if enable = '0' then
            pos_r <= (others => '0');
          else
            pos_r <= pos_r + 1;
          end if;
        else
          pos_r    <= (others => '0');
          uftick_r <= '1';

          -- A SOF is transmitted in EVERY microframe, carrying the same
          -- number eight times over, so the request is renewed per
          -- microframe and so is the missed-SOF report.
          if sofreq_r = '1' and sof_ack = '0' then
            missed_r <= '1';
          end if;
          sofreq_r <= '1';

          case bnd is
            when B_UFRAME =>
              uf_r <= uf_r + 1;
            when B_FRAME =>
              uf_r    <= 0;
              ftick_r <= '1';
              -- The frame number advances HERE: once per eight microframes.
              if frame_r = FRAME_MAX then
                frame_r <= (others => '0');
                wrap_r  <= '1';
              else
                frame_r <= frame_r + 1;
              end if;
            when B_NONE =>
              null;   -- unreachable here; stated so the case is exhaustive
          end case;
        end if;
      end if;
    end if;
  end process;
end architecture;

uf_r is integer range 0 to 7, and that is the consequential line. §11 measures what it does when a mutation pushes the index past its limit: the simulation stops, naming the signal and the line, where the other two languages wrap silently. Chapter 16.5 found the same property on a different counter, and finding it twice in two modules is what makes it a property of the language rather than a coincidence.

8. Comparing the Three

ConcernVerilogSystemVerilogVHDL
The nestingat_fbound derived from at_uboundbnd_e — the enum is the nestingbnd_t, no encoding
The microframe indexreg [2:0], wraps silentlylogic [2:0], sameinteger range 0 to 7 — range-checked
Illegal parameterisationundetectedthree $fatal guardsthree assert ... severity failure
Case exhaustivenessnot checkedunique — but see §18enforced by the type

9. The Testbenches

The model keeps ONE absolute microframe count and derives both quantities by division:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      check(sof_frame_no === FRAME_W'((ubounds/UF_MOD) % FRAME_MOD), "sof_frame_no");
      check(uframe       === 3'(ubounds % UF_MOD),                   "uframe");

A nesting error cannot be reproduced by a division of one total. A frame number advancing per microframe, an index failing to roll over, a frame boundary in the wrong place — each breaks the relationship between the two quantities, and the model derives both from a single number that has no such relationship to break.

The directed sequence is built around §2's property:

ScenarioWhat it pins down
one microframe boundaryit is not a frame boundary; the index advances and the number does not
a whole frame's worththe number is unchanged across the microframes
a counted runeight microframe ticks per frame tick, exactly
three framesevery index 0–7 is visited
every frame boundarylands exactly where the index rolls to zero
enable lowboth quantities held, position parked
bus_reset at a non-zero indexboth restart — see below
2048 frames × 8the nested wrap, directed
4000 randomised acksindependently drawn

10. Mutation Testing — Across All Three Languages

IDMutationVerilogSystemVerilogVHDLKilled
—baseline, no mutation000—
U1the frame number advances every microframe146281146281146281✅ all three
U2the index wraps one late (0…8)146281146281FATAL✅ all three — see §11
U3every microframe tick is also a frame tick152981529815298✅ all three
U4the index is never reset at a frame boundary146178146178146178✅ all three
U5the frame boundary is placed at index 0146281146281146281✅ all three
U6bus_reset does not clear the index99993see note99993✅ after §9's fix

U6's SystemVerilog cell is a tooling limit, not a missing result. Verilog and VHDL both measure 99 993, against 0 before §9's stimulus fix. The SystemVerilog run of the same mutation, with the same stimulus, had not completed after fourteen minutes of CPU when this chapter was finalised — against ten seconds for Verilog and about a minute for VHDL.

That 80× spread is worth recording as a tooling observation rather than hidden. The three benches drive identical directed stimulus and the baseline runs are comparable — the SystemVerilog baseline is in fact the fastest of the three at about a second. It is only the failing path that diverges, and only under Icarus: a mutation producing roughly a hundred thousand failures costs seconds in one language front-end and more than a quarter of an hour in another, on the same simulator and the same machine.

The cell says "see note" because an unmeasured figure is an invented figure. Chapter 16.5 §20 declined to invent a count for a fatal VHDL run for exactly this reason, and the discipline is the same one: report what the tool actually produced.

U1 is the chapter's thesis measured. Advancing the frame number eight times too fast costs 146 281 failures — the largest count in Module 17 — because it breaks the relationship between the two quantities on every single microframe, not at a boundary.

U3's much smaller 15 298 is worth reading. Asserting frame_tick on every microframe leaves both counters correct; only the tick is wrong. The quantities are right and the event is wrong — which is exactly the kind of defect that survives a bench checking values but not edges.

11. The Mutation the Type System Caught

U2 does not produce a failure count in VHDL. It stops the simulation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
** Fatal: 335ns+0: value 8 outside of INTEGER range 0 to 7 for signal UF_R
     > uf_vhdl_U2.vhd:123
     |
 123 |               uf_r <= uf_r + 1;
     |                       ^^^^^^^^

At 335 nanoseconds, naming the signal, the line and the illegal value. The Verilog and SystemVerilog versions wrap a 3-bit counter from 7 to 0 silently and produce 146 281 checker failures much later, downstream of the corruption.

12. The Waveform

Eight microframes, one frame number

9 cycles
A waveform showing nine consecutive microframe boundaries in the high-speed time base. The microframe index advances through zero, one, two, three, four, five, six and seven across the first eight columns, and a microframe tick is asserted at every one of them. The frame number that the start-of-frame token carries remains at four hundred and twelve throughout all eight, because the frame number advances once per millisecond and not once per microframe. At the ninth boundary the index rolls over from seven back to zero, the frame tick is asserted in addition to the microframe tick, and only then does the frame number advance to four hundred and thirteen. A device that identified a scheduling opportunity by frame number alone would be unable to distinguish the first eight columns from one another.the frame number is set here…the frame number is sethere……and is STILL the same eight opportunities later…and is STILL the sameeight opportunities laterindex rolls 7→0: frame_tick, and only now +1index rolls 7→0:frame_tick, and only now +1microframe boundary012345678uframe_tickuframe012345670sof_frame_no412412412412412412412412413frame_tickt0t1t2t3t4t5t6t7t8
Figure 2 — nine consecutive microframe boundaries. The frame number is constant across the first eight and advances only at the ninth, which is the property the whole chapter exists to establish.

This waveform is in the controller clock domain and one column is one microframe boundary, not one clock. The underlying simulation ran two controller clocks per microframe; the columns here are the boundaries themselves, because the boundaries are what a scheduler sees.

Columns 0 through 7 are the chapter. Eight distinct scheduling opportunities, eight SOF packets, one frame number. A controller that keyed its schedule on sof_frame_no alone would place work in some one of those eight — and would appear to work.

13. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // A1. THE property of this chapter: the frame number changes ONLY at a
  //     frame boundary -- never at an ordinary microframe boundary.
  property p_number_only_at_frame;
    @(posedge clk) disable iff (!rst_n || bus_reset)
      !frame_tick |=> $stable(sof_frame_no);
  endproperty
  a_number_only_at_frame: assert property (p_number_only_at_frame);

  // A2. SAFETY: a frame boundary is ALWAYS also a microframe boundary. The
  //     nesting stated as an implication, so a design that produced one
  //     without the other would fail immediately.
  property p_frame_implies_uframe;
    @(posedge clk) disable iff (!rst_n)
      frame_tick |-> uframe_tick;
  endproperty
  a_frame_implies_uframe: assert property (p_frame_implies_uframe);

  // A3. SAFETY: a frame boundary lands exactly where the index rolls to 0.
  property p_frame_at_index_zero;
    @(posedge clk) disable iff (!rst_n || bus_reset)
      frame_tick |-> (uframe == 3'd0);
  endproperty
  a_frame_at_index_zero: assert property (p_frame_at_index_zero);

  // A4. BOUNDEDNESS -- the property VHDL's subtype enforces for free.
  property p_index_in_range;
    @(posedge clk) disable iff (!rst_n)
      (uframe <= 3'd7);
  endproperty
  a_index_in_range: assert property (p_index_in_range);

  // A5. COUNTING: exactly eight microframe ticks between frame ticks.
  //     Written against the bench's own tick count, not the DUT's index.
  property p_eight_per_frame;
    @(posedge clk) disable iff (!rst_n || bus_reset)
      frame_tick |-> (uticks_since_last_frame == 8);
  endproperty
  a_eight_per_frame: assert property (p_eight_per_frame);

  // A6. PROGRESS: an enabled port eventually produces a frame boundary. A
  //     timer that ticks microframes for ever without ever rolling the index
  //     satisfies A1, A2, A3 and A4 perfectly -- and is mutation U4.
  property p_eventually_frames;
    @(posedge clk) disable iff (!rst_n || bus_reset)
      enable |-> ##[1:(8*TICKS_PER_UFRAME)] frame_tick;
  endproperty
  a_eventually_frames: assert property (p_eventually_frames);

Assertion contracts

ClaimSafety / progressVacuity riskHow non-vacuity is established
A1the number changes only at a frame boundarysafetynone — most cycles are not boundariesholds constantly
A2a frame boundary is also a microframe boundarysafetymoderate — needs frame boundaries2183 frame ticks measured
A3a frame boundary lands at index 0safetymoderate — same antecedentsame
A4the index stays in 0–7safetynone — no antecedentholds every cycle
A5exactly eight microframes per framesafetymoderate2183 frame ticks
A6an enabled port eventually framesprogresslowenable is high for almost the whole run

A6 is the only progress property and it is the one that catches U4. An index that never resets never reaches 7 again, so frame_tick never fires again — and A1, A2, A3 and A4 all continue to hold perfectly, because a design that stops producing boundaries violates no property phrased as when a boundary occurs…. Chapter 17.1 §14 made the same argument about a timer that stops; this is the same failure one level up.

A4 is exactly what VHDL's integer range 0 to 7 provides without being written (§11) — and in the Verilog and SystemVerilog it must be written, because a 3-bit vector makes the property trivially true while the intent is that the index never attempt to exceed 7.

14. Verification: Still Not a UVM Problem

Chapter 17.1 §15 declined UVM for a counter and promised it would arrive in 17.3. This chapter is still a counter — two of them — and the same argument applies unchanged.

The input space is three bits and two parameters. The interesting scenarios are the nesting property, the index walk, the reset-from-non-zero case §9 found, and the nested wrap. Every one of them is a directed test, and §9 is the evidence that the directed tests are where the value was: the randomised phase contributed nothing to U6 and the fix was two lines of directed stimulus.

Where the scenario space genuinely begins is the next chapter, where multiple flows with different service requirements compete for one of these opportunities and the question becomes which one should have been chosen, and did the losers survive.

15. Debugging: the Device That Polls at the Right Rate and Still Jitters

A high-speed interrupt device requests a 125 µs interval. Measured over a second, it is polled almost exactly 8000 times — the correct rate. But the interval between consecutive polls varies from under 125 µs to nearly 1 ms, and an application sampling the device sees jitter it cannot explain.

The rate is right and the spacing is wrong, which is a very specific signature. A controller placing eight polls per millisecond at arbitrary microframes within each millisecond produces exactly this: the right count, the wrong distribution.

And §2 named the cause before any instrument is attached. A scheduler that keys its periodic placement on the frame number rather than on the microframe index has eight positions to choose from and no reason to prefer one, so the poll lands wherever the frame's work happened to leave room. Correct rate, uncontrolled phase.

The chain, from the outside in:

Analyser — measure the interval distribution, not the count. "8000 polls per second" is consistent with both correct and broken behaviour. The histogram of gaps distinguishes them immediately.

Analyser — which microframe do the polls land in? Count SOFs since the last frame-number change to recover the index (§2: it is not transmitted). A device polled at a consistent index is correctly scheduled; one scattered across indices is not.

Controller — is the schedule indexed by microframe at all? If the scheduling structure has one entry per frame rather than eight, the controller cannot express which microframe, and no amount of tuning will fix it. That is an architecture problem, not a parameter.

RTL — the first divergence. Compare the bench's independent (ubounds mod 8) against the design's uframe at each boundary. The first boundary where they differ is the bug — and in practice it is U1 (the number advancing per microframe), U4 (the index never rolling) or U5 (the frame boundary in the wrong place).

16. Common Misconceptions

"High speed just runs the frame counter eight times faster." The frame number advances once per millisecond at both speeds (§2). Mutation U1 is exactly this belief and costs 146 281 failures.

"The SOF tells the device which microframe it is." The frame-number field is the same across all eight (§2); the index is derived by counting SOFs.

"A microframe is a small frame." It is a scheduling opportunity inside a frame. Eight of them share one frame number, and bInterval is denominated in them at high speed (§1).

"Frame boundaries and microframe boundaries are separate events." A frame boundary is a microframe boundary — the one that rolls the index (§3). Treating them as independent invites two counters that drift.

"A disabled port should restart its microframe index." Both quantities are protocol state and both are held (§4). Only the local position is parked.

"If the poll rate is right, the scheduling is right." §15: the right rate with an uncontrolled index produces up to 875 µs of jitter on a 125 µs request.

"Testing reset once is enough." §9: a reset applied only from an already-clear state proves nothing about clearing.

17. Exercises

1. A controller runs at 60 MHz. Compute TICKS_PER_UFRAME and TICKS_PER_FRAME, and confirm the second is eight times the first. Then determine what happens if a designer parameterises both independently and they disagree by one.

2. §2 says the microframe index is derived by counting SOFs between frame-number changes. Write the device-side logic that recovers it, and determine what it does after a missed SOF.

3. Re-run mutation U6 against the original stimulus (one bus reset, at index 0) and against the fixed stimulus, and explain the difference in terms of observability rather than coverage.

4. VHDL's integer range 0 to 7 caught U2 (§11). Add an equivalent run-time check to the SystemVerilog without changing the design's behaviour, and say what it costs and when it fires relative to the checker.

5. Write an SVA property that catches U5 — the frame boundary placed at index 0 instead of 7 — without referring to the design's uframe. State what external reference it needs.

6. §15's device is polled at the right rate with the wrong phase. Design the smallest change to a frame-indexed schedule that makes the microframe position controllable, and say what it costs in storage.

18. Summary

High speed puts eight 125 µs scheduling opportunities inside each 1 ms frame (§1), and the Linux core states the relationship directly: microframes (eight per millisecond) rather than in frames (one per millisecond).

The frame number does not advance eight times faster (§2). It advances once per millisecond, so eight consecutive SOFs carry the same value, and the microframe index is derived by counting them rather than received. A controller that identifies an opportunity by frame number alone has eight ways to be wrong, and the symptom is jitter rather than failure (§15).

A frame boundary is the microframe boundary that rolls the index (§3) — derived, not counted separately, because two independent counters for one millisecond will eventually disagree.

All three HDL implementations were simulated (§19), and six mutations died in all three (§10). U1 — advancing the number per microframe — costs 146 281 failures, the largest count in the module.

U2 does not produce a count in VHDL at all (§11). integer range 0 to 7 stops the simulation at 335 ns naming the signal and the line, where the other two wrap silently. This is the second module in which a range-constrained subtype has caught a counter defect the other languages could only observe as a downstream symptom — which makes it a language property rather than a coincidence.

And U6 first survived entirely (§9), because the whole run applied one bus reset and the index happened to be zero when it did. A clearing operation tested only from an already-clear state proves nothing. Two lines of directed stimulus took it from 0 to 99 993, measured identically in Verilog and VHDL.

19. Tooling, Honestly

LanguageDesignTestbenchAnalysed / compiledSimulatedMutations
Verilog-2005usb_uframe_timeruf_v_tb.v✅ Icarus -g2005✅ 0 errors✅ all six
SystemVerilogusb_uframe_timer_svuf_sv_tb.sv✅ Icarus -g2012✅ 0 errors✅ all six
VHDL-2008usb_uframe_timer_vhdluf_vhdl_tb.vhd✅ nvc 1.23.0✅ 0 errors✅ all six
SVA (§13)——❌ unsupported by Icarus❌—

Two cells in §10's table are not numbers, and both are honest rather than missing. U2's VHDL result is a fatal range violation (§11), recorded as FATAL rather than invented as a count. U6's SystemVerilog result is a tooling limit: the same mutation and stimulus measure 99 993 in ten seconds under Verilog and about a minute under nvc, and had not completed after fourteen minutes of CPU under Icarus SystemVerilog. An unmeasured figure is an invented figure, so the cell says so.

The SystemVerilog bench is generated from the Verilog bench's scenario list, so the two drive identical stimulus — deliberate for a time base, and the reason their counts agree exactly. The VHDL bench uses uniform and diverges only in the randomised phase.

20. What Comes Next

Two chapters have now built time: a frame, and eight microframes inside it. Every one of them is an opportunity — a moment at which the host may place work on the bus.

Nothing so far decides what to place.

Chapter 17.3 is that decision, and it is the module's centre. Several flows are waiting, each with a different requirement: an isochronous endpoint holding a reservation that 16.4 admitted, an interrupt endpoint whose 15.4 deadline is approaching, a control transfer that must never be starved, and bulk traffic that will take whatever is left.

They cannot all go first. The chapter takes that apart into the decisions it actually is — what is pending, what is eligible, what does policy require, what does the budget permit, and who wins — and shows that the most common scheduler defect is treating any two of those as the same question.

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.