Skip to content
VLSI Mentor

USB · Module 27

What Is USB?

The opening interview question answered with one load-bearing idea instead of a list — USB is host-scheduled, and polling, NAK, the frame and the missing interrupt line are all consequences of it.

This is the first question in almost every USB interview, it is always asked, and it is almost never the question it appears to be.

1. What Is Actually Being Asked

"What is USB?" is not a request for the expansion of the acronym. The interviewer already knows. What they are measuring is whether you can compress a large protocol into its one load-bearing idea — because that is the skill the job needs, and ninety seconds of talking is a good way to find out whether you have it.

There are two failure modes and they are both common.

The list. "USB is a serial bus, it's hot-pluggable, it has four transfer types, it uses differential signalling, there's USB 1.1, 2.0, 3.x, USB-C…" This is all true, and it demonstrates that you have read a summary. Nothing in it is organising; any item could be removed without the rest changing.

The layer diagram. Starting at the physical layer and working up. This is how specifications are written and it is the wrong order for a person: it spends the first sixty seconds on NRZI and bit stuffing and never arrives at what the bus is for.

2. The Ninety-Second Answer

USB is a host-scheduled bus. There is exactly one host, it is the only thing on the bus that may start a transfer, and every device is strictly a responder: it transmits only in the window immediately after the host has addressed it, and when addressed it must answer.

That single asymmetry produces everything else. Because a device cannot initiate, there is no interrupt line from device to host — a device with urgent news must wait to be asked, so the host polls, and "interrupt transfers" in USB are a polling guarantee rather than an interrupt. Because the device must answer even when it has nothing, there is a handshake that means "ask me again", which is NAK. Because the host has to fit all its polling into a repeating budget, there is a frame: 1 ms in USB 2.0 full-speed, 125 µs microframes at high speed. And because the host alone decides who talks and when, collisions are not arbitrated — they are impossible by construction, and a bus with two talkers is not a busy bus, it is a broken one.

Everything else — the four transfer types, the endpoint model, enumeration, the tree of hubs — is the machinery that makes a single-initiator bus usable for keyboards and cameras and disks at the same time.

That is the answer. It is roughly seventy seconds spoken, it contains one idea, and it has already pre-answered the four most likely follow-ups.

3. Why "Host-Scheduled" Is the Right Centre

The test of an organising idea is whether the surrounding facts become consequences rather than additions. Here they do:

Fact about USBConsequence of what
No device-to-host interrupt linea device cannot initiate
Interrupt transfers are polledsame
NAK exists at allthe device must answer even when empty
Frames / microframesthe host must budget its polling
No collision detectionone initiator means no collisions
Devices have addresses; the host does notonly the host sends tokens
Enumeration is host-driventhe device cannot announce itself
A hub is a repeater, not a switchthe tree has one root
Isochronous has no retrya retry needs a slot the schedule did not reserve

4. What We Are Building

An answer you can only say is worth less than one you can build. So the rest of this chapter is the sentence above turned into hardware: the smallest module that makes the defining property true rather than merely stated.

usb_token_gate sits between what a device wants to transmit and what it is permitted to transmit. Those are two different things, and the entire module is the difference.

What the device wants, and what it is allowed

A USB host sends a token to the token gate, which is the only path by which a device's pending data may reach the busHostIN tokenusb_token_gateDevice transmitsEndpoint bufferNo token, no TXschedulesgrants 1 cyclepermittedasksotherwise12
data_avail is an input that the gate ignores except in the single cycle after a token addressed to this device. A full buffer confers no rights.

The two edges out of the buffer are the design. One of them requires a token; the other is what happens the rest of the time, which is nothing.

A token, an answer, and silence

Timing of a token, the single-cycle response that follows it, and an idle cycle in which data is available but no token arrivesaddressed: answer dueaddressed: answer duenot addressed: silentnot addressed: silenttoken for ustoken for usthe one cycle it may talkthe one cycle it may talktoken for device 0x55token for device 0x55data ready, no token, silencedata ready, no token,silenceclktok_validtok_addr02A2A2A5555555555data_availarmedtx_validtx_pid000DATADATADATADATADATADATAt0t1t2t3t4t5t6t7t8
The response appears one cycle after the token and lasts exactly one cycle. Cycle 6 has data available and no token, and the bus stays idle.

Note data_avail in that waveform. It is high the entire time. The device has something to send in every single cycle and sends it in exactly one of them.

NAK is a loop, not a refusal

Repeated IN tokens to an empty endpoint each produce a NAK, and the first token after data arrives produces DATAempty: NAK every pollempty: NAK every pollready: DATAready: DATANAK: ask me againNAK: ask me againstill empty, still NAKstill empty, still NAKdata arrived: DATAdata arrived: DATAclktok_validdata_availtx_validtx_pid0NAKNAKNAKNAKNAKNAKDATADATADATAt0t1t2t3t4t5t6t7t8t9
Polled while empty, the device NAKs every time — not once. The moment data arrives the same poll returns DATA. That loop is how a device that cannot initiate expresses not-ready.

A single NAK proves nothing — a design that NAKs permanently produces exactly the same first cycle. The property is that NAK is neither sticky nor terminal, and it takes repeated polling to test. Section 13 shows what happens to a mutation score when you do not.

5. Seven Properties

Everything the module is claimed to do, written down before the code, so that "it works" has a definition:

#Property
1The device transmits only in the cycle after a token addressed to it. There is no other path.
2A token for another address produces silence, and the silence is counted.
3SOF is answered by nobody. It is a broadcast timestamp addressed to no device.
4An IN token gets exactly one of three answers: DATA, NAK, or STALL — never nothing.
5STALL takes priority over DATA: a halted endpoint is halted even with a full buffer.
6A SETUP token is never stalled, halted or not — it is how software clears a halt.
7A token superseded by another token is never answered.

Property 7 is the one nobody writes on a whiteboard and it is a real failure: if the host asks, changes its mind, and asks something else, a device that answers the first question is transmitting into a slot that now belongs to another device.

6. Verilog-2005 RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_token_gate -- "What is USB?" answered in hardware.
//
//  Ninety seconds is enough for one idea, and there is only one that
//  explains the rest of the protocol:
//
//      A USB device may never initiate a transfer.
//
//  It speaks only when the host has just addressed it, and it must
//  say SOMETHING when it does. Every other feature of USB falls out
//  of that: the polling, the NAK handshake that means "ask me again",
//  the frame that bounds how often you are asked, and the complete
//  absence of an interrupt line from device to host.
//
//  This module is the gate that enforces it. It sits between what a
//  device WANTS to transmit and what it is ALLOWED to transmit, and
//  the whole design is the difference between those two things.
// =====================================================================
module usb_token_gate #(
  parameter [6:0] DEV_ADDR = 7'h2A,
  parameter       N_EP     = 4
) (
  input  wire              clk,
  input  wire              rst_n,

  // ---- the bus, as the device sees it ----
  input  wire              tok_valid,
  input  wire [1:0]        tok_pid,      // T_OUT / T_IN / T_SOF / T_SETUP
  input  wire [6:0]        tok_addr,
  input  wire [1:0]        tok_ep,

  // ---- what the device would LIKE to do, every cycle ----
  //
  // Note that this is an INPUT and it is completely ignored except in
  // the one cycle after a token. A device with a full transmit buffer
  // and an urgent interrupt to report has exactly the same rights as
  // an idle one: none.
  input  wire [N_EP-1:0]   data_avail,
  input  wire [N_EP-1:0]   halted,

  // ---- what it is ALLOWED to do ----
  output wire              tx_valid,
  output wire [2:0]        tx_pid,       // R_NONE / R_DATA / R_NAK / R_STALL / R_ACK
  output wire [1:0]        tx_ep,

  // ---- observability: the counters ARE the proof ----
  output wire [31:0]       n_resp,
  output wire [31:0]       n_ignored,
  output wire [31:0]       n_nak,
  output wire [31:0]       n_stall,
  output wire [31:0]       n_superseded
);

  localparam [1:0] T_OUT   = 2'd0,
                   T_IN    = 2'd1,
                   T_SOF   = 2'd2,
                   T_SETUP = 2'd3;

  localparam [2:0] R_NONE  = 3'd0,
                   R_DATA  = 3'd1,
                   R_NAK   = 3'd2,
                   R_STALL = 3'd3,
                   R_ACK   = 3'd4;

  // ---- the entire state of the gate ----
  //
  // `armed` is the permission slip. It is set ONLY by a token that was
  // addressed to this device, it lasts exactly one cycle, and nothing
  // else in the design can set it.
  reg        armed;
  reg [1:0]  arm_ep;
  reg [1:0]  arm_pid;

  reg        tx_valid_r;
  reg [2:0]  tx_pid_r;
  reg [1:0]  tx_ep_r;

  reg [31:0] resp_r, ign_r, nak_r, stall_r, sup_r;

  assign tx_valid = tx_valid_r;
  assign tx_pid   = tx_pid_r;
  assign tx_ep    = tx_ep_r;

  assign n_resp       = resp_r;
  assign n_ignored    = ign_r;
  assign n_nak        = nak_r;
  assign n_stall      = stall_r;
  assign n_superseded = sup_r;

  // ---- is this token ours, and does it want an answer? ----
  //
  // Three separate questions, deliberately not collapsed into one
  // expression, because each is a different way to get this wrong:
  //
  //   for_us    -- the address check. Dropping it makes the device
  //                answer for every other device on the bus.
  //   wants_rsp -- SOF is a broadcast timestamp. It is addressed to
  //                nobody and answered by nobody.
  //   in_dir    -- an IN token asks for data; OUT/SETUP deliver it and
  //                get a handshake instead.
  wire for_us    = tok_valid && (tok_addr == DEV_ADDR);
  wire wants_rsp = (tok_pid != T_SOF);
  wire in_dir    = (tok_pid == T_IN);

  wire accept    = for_us && wants_rsp;

  integer i;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      armed      <= 1'b0;
      arm_ep     <= 2'd0;
      arm_pid    <= T_OUT;
      tx_valid_r <= 1'b0;
      tx_pid_r   <= R_NONE;
      tx_ep_r    <= 2'd0;
      resp_r     <= 32'd0;
      ign_r      <= 32'd0;
      nak_r      <= 32'd0;
      stall_r    <= 32'd0;
      sup_r      <= 32'd0;
    end else begin
      // ---- the response, driven from `armed` and NOTHING else ----
      //
      // Written first so that the arming logic below can overwrite it
      // in the same cycle a new token arrives. The ordering matters:
      // a token arriving while armed SUPERSEDES the old one, and the
      // old response is never sent. A device that answers a token the
      // host has already moved on from is transmitting into somebody
      // else's slot.
      tx_valid_r <= 1'b0;
      tx_pid_r   <= R_NONE;
      tx_ep_r    <= arm_ep;

      if (armed) begin
        tx_valid_r <= 1'b1;
        tx_ep_r    <= arm_ep;
        resp_r     <= resp_r + 32'd1;

        if (arm_pid == T_IN) begin
          // ---- an IN token: three legal answers and no fourth ----
          //
          // STALL is checked FIRST. A halted endpoint is halted whether
          // or not it happens to have data sitting in its buffer, and
          // sending that data would resume an endpoint that software
          // has deliberately stopped.
          if (halted[arm_ep]) begin
            tx_pid_r <= R_STALL;
            stall_r  <= stall_r + 32'd1;
          end else if (data_avail[arm_ep]) begin
            tx_pid_r <= R_DATA;
          end else begin
            // NAK is not an error. It is the device saying "ask me
            // again" -- which is the only way a device that cannot
            // initiate is able to express not-ready at all.
            tx_pid_r <= R_NAK;
            nak_r    <= nak_r + 32'd1;
          end
        end else begin
          // OUT / SETUP: the host supplied the data, the device
          // acknowledges. A halted endpoint still stalls.
          if (halted[arm_ep] && (arm_pid != T_SETUP)) begin
            tx_pid_r <= R_STALL;
            stall_r  <= stall_r + 32'd1;
          end else begin
            tx_pid_r <= R_ACK;
          end
        end
      end

      // ---- arming: the ONLY path to permission ----
      if (tok_valid) begin
        if (accept) begin
          if (armed) sup_r <= sup_r + 32'd1;
          armed   <= 1'b1;
          arm_ep  <= tok_ep;
          arm_pid <= tok_pid;
        end else begin
          // A token for another address, or an SOF. The device stays
          // silent, and the silence is counted so a run can prove it
          // saw traffic it correctly declined to answer.
          if (armed) sup_r <= sup_r + 32'd1;
          armed <= 1'b0;
          ign_r <= ign_r + 32'd1;
        end
      end else begin
        // No token this cycle. Permission expires. There is no path
        // through this module by which `armed` becomes 1 here, and
        // that absence is the entire point of the design.
        armed <= 1'b0;
      end
    end
  end

endmodule

7. SystemVerilog RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_token_gate -- SystemVerilog.
//
//  Same design, same property, and two things the Verilog cannot say:
//  the PIDs are named types rather than magic numbers, and the one
//  rule the whole module exists for is written down as a function of
//  the token rather than as a comment above it.
// =====================================================================
package tg_pkg;
  typedef enum logic [1:0] { T_OUT = 2'd0, T_IN = 2'd1,
                             T_SOF = 2'd2, T_SETUP = 2'd3 } tok_e;

  typedef enum logic [2:0] { R_NONE  = 3'd0, R_DATA = 3'd1, R_NAK = 3'd2,
                             R_STALL = 3'd3, R_ACK  = 3'd4 } rsp_e;
endpackage

module usb_token_gate
  import tg_pkg::*;
#(
  parameter logic [6:0] DEV_ADDR = 7'h2A,
  parameter int         N_EP     = 4
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              tok_valid,
  input  logic [1:0]        tok_pid,
  input  logic [6:0]        tok_addr,
  input  logic [1:0]        tok_ep,

  // What the device would LIKE to do, every cycle -- and which is
  // ignored except in the one cycle after a token addressed to it.
  input  logic [N_EP-1:0]   data_avail,
  input  logic [N_EP-1:0]   halted,

  output logic              tx_valid,
  output logic [2:0]        tx_pid,
  output logic [1:0]        tx_ep,

  output logic [31:0]       n_resp,
  output logic [31:0]       n_ignored,
  output logic [31:0]       n_nak,
  output logic [31:0]       n_stall,
  output logic [31:0]       n_superseded
);

  logic       armed;
  logic [1:0] arm_ep, arm_pid;

  logic [31:0] resp_r, ign_r, nak_r, stall_r, sup_r;

  assign n_resp       = resp_r;
  assign n_ignored    = ign_r;
  assign n_nak        = nak_r;
  assign n_stall      = stall_r;
  assign n_superseded = sup_r;

  // ---- the rule, as a function ----
  //
  // A token grants the right to transmit if and only if it names this
  // device and is not the broadcast frame marker. Written as a function
  // so that it has exactly one definition and one place to be wrong.
  function automatic logic grants(logic valid, logic [6:0] a, logic [1:0] p);
    return valid && (a == DEV_ADDR) && (p != T_SOF);
  endfunction

  wire for_us    = tok_valid && (tok_addr == DEV_ADDR);
  wire wants_rsp = (tok_pid != T_SOF);
  wire accept    = for_us && wants_rsp;

  // The response for an armed IN token. A function rather than a
  // nested ternary because Icarus will not take an enum-valued
  // conditional, and because the priority -- STALL before DATA -- is
  // a decision worth seeing on its own line.
  function automatic logic [2:0] in_response(logic h, logic d);
    if (h) return R_STALL;
    if (d) return R_DATA;
    return R_NAK;
  endfunction

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      armed    <= 1'b0;
      arm_ep   <= 2'd0;
      arm_pid  <= T_OUT;
      tx_valid <= 1'b0;
      tx_pid   <= R_NONE;
      tx_ep    <= 2'd0;
      resp_r   <= '0;
      ign_r    <= '0;
      nak_r    <= '0;
      stall_r  <= '0;
      sup_r    <= '0;
    end else begin
      tx_valid <= 1'b0;
      tx_pid   <= R_NONE;
      tx_ep    <= arm_ep;

      if (armed) begin
        tx_valid <= 1'b1;
        tx_ep    <= arm_ep;
        resp_r   <= resp_r + 32'd1;

        if (arm_pid == T_IN) begin
          tx_pid <= in_response(halted[arm_ep], data_avail[arm_ep]);
          if      (halted[arm_ep])     stall_r <= stall_r + 32'd1;
          else if (!data_avail[arm_ep]) nak_r  <= nak_r   + 32'd1;
        end else begin
          // A SETUP is never stalled. The control endpoint is how
          // software clears a halt in the first place, so a device
          // that stalls SETUP has locked itself out permanently.
          if (halted[arm_ep] && (arm_pid != T_SETUP)) begin
            tx_pid  <= R_STALL;
            stall_r <= stall_r + 32'd1;
          end else begin
            tx_pid <= R_ACK;
          end
        end
      end

      if (tok_valid) begin
        if (accept) begin
          if (armed) sup_r <= sup_r + 32'd1;
          armed   <= 1'b1;
          arm_ep  <= tok_ep;
          arm_pid <= tok_pid;
        end else begin
          if (armed) sup_r <= sup_r + 32'd1;
          armed <= 1'b0;
          ign_r <= ign_r + 32'd1;
        end
      end else begin
        armed <= 1'b0;
      end
    end
  end

endmodule

8. VHDL-2008 RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  usb_token_gate -- VHDL-2008.
--
--  The third language is not decoration. Porting a design forces every
--  implicit assumption in it to be written down, and a property that
--  survives three independent expressions is a property rather than an
--  artefact of one compiler's idea of what you meant.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package tg_pkg is
  -- Deliberately NOT named T_OUT / R_NONE: VHDL identifiers are case
  -- insensitive, so a package constant and a port that differ only in
  -- case are the same name, and the port silently wins.
  constant TOK_OUT   : std_logic_vector(1 downto 0) := "00";
  constant TOK_IN    : std_logic_vector(1 downto 0) := "01";
  constant TOK_SOF   : std_logic_vector(1 downto 0) := "10";
  constant TOK_SETUP : std_logic_vector(1 downto 0) := "11";

  constant RSP_NONE  : std_logic_vector(2 downto 0) := "000";
  constant RSP_DATA  : std_logic_vector(2 downto 0) := "001";
  constant RSP_NAK   : std_logic_vector(2 downto 0) := "010";
  constant RSP_STALL : std_logic_vector(2 downto 0) := "011";
  constant RSP_ACK   : std_logic_vector(2 downto 0) := "100";
end package;

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

entity usb_token_gate is
  generic (
    DEV_ADDR : std_logic_vector(6 downto 0) := "0101010";
    N_EP     : natural := 4
  );
  port (
    clk        : in  std_logic;
    rst_n      : in  std_logic;

    tok_valid  : in  std_logic;
    tok_pid    : in  std_logic_vector(1 downto 0);
    tok_addr   : in  std_logic_vector(6 downto 0);
    tok_ep     : in  std_logic_vector(1 downto 0);

    data_avail : in  std_logic_vector(N_EP-1 downto 0);
    halted     : in  std_logic_vector(N_EP-1 downto 0);

    tx_valid   : out std_logic;
    tx_pid     : out std_logic_vector(2 downto 0);
    tx_ep      : out std_logic_vector(1 downto 0);

    n_resp       : out std_logic_vector(31 downto 0);
    n_ignored    : out std_logic_vector(31 downto 0);
    n_nak        : out std_logic_vector(31 downto 0);
    n_stall      : out std_logic_vector(31 downto 0);
    n_superseded : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of usb_token_gate is
  signal armed   : std_logic := '0';
  signal arm_ep  : std_logic_vector(1 downto 0) := "00";
  signal arm_pid : std_logic_vector(1 downto 0) := TOK_OUT;

  signal resp_r, ign_r, nak_r, stall_r, sup_r : unsigned(31 downto 0)
    := (others => '0');

  signal for_us, wants_rsp, accept_tok : std_logic;
begin

  for_us      <= '1' when (tok_valid = '1' and tok_addr = DEV_ADDR) else '0';
  wants_rsp   <= '1' when (tok_pid /= TOK_SOF) else '0';
  accept_tok  <= for_us and wants_rsp;

  n_resp       <= std_logic_vector(resp_r);
  n_ignored    <= std_logic_vector(ign_r);
  n_nak        <= std_logic_vector(nak_r);
  n_stall      <= std_logic_vector(stall_r);
  n_superseded <= std_logic_vector(sup_r);

  process(clk, rst_n)
    variable ep_i : natural;
  begin
    if rst_n = '0' then
      armed    <= '0';
      arm_ep   <= "00";
      arm_pid  <= TOK_OUT;
      tx_valid <= '0';
      tx_pid   <= RSP_NONE;
      tx_ep    <= "00";
      resp_r   <= (others => '0');
      ign_r    <= (others => '0');
      nak_r    <= (others => '0');
      stall_r  <= (others => '0');
      sup_r    <= (others => '0');

    elsif rising_edge(clk) then
      tx_valid <= '0';
      tx_pid   <= RSP_NONE;
      tx_ep    <= arm_ep;

      if armed = '1' then
        ep_i := to_integer(unsigned(arm_ep));

        tx_valid <= '1';
        tx_ep    <= arm_ep;
        resp_r   <= resp_r + 1;

        if arm_pid = TOK_IN then
          -- STALL is tested first: a halted endpoint stays halted even
          -- when its buffer happens to be full.
          if halted(ep_i) = '1' then
            tx_pid  <= RSP_STALL;
            stall_r <= stall_r + 1;
          elsif data_avail(ep_i) = '1' then
            tx_pid <= RSP_DATA;
          else
            tx_pid <= RSP_NAK;
            nak_r  <= nak_r + 1;
          end if;
        else
          if halted(ep_i) = '1' and arm_pid /= TOK_SETUP then
            tx_pid  <= RSP_STALL;
            stall_r <= stall_r + 1;
          else
            tx_pid <= RSP_ACK;
          end if;
        end if;
      end if;

      if tok_valid = '1' then
        if accept_tok = '1' then
          if armed = '1' then sup_r <= sup_r + 1; end if;
          armed   <= '1';
          arm_ep  <= tok_ep;
          arm_pid <= tok_pid;
        else
          if armed = '1' then sup_r <= sup_r + 1; end if;
          armed <= '0';
          ign_r <= ign_r + 1;
        end if;
      else
        -- No token. Permission expires, and there is no other path by
        -- which it is granted.
        armed <= '0';
      end if;
    end if;
  end process;

end architecture;

9. How the Testbench Knows the Answer

The shadow model is deliberately not a second copy of the design's state machine. The design asks "am I armed?". The shadow asks "was the token in the previous cycle addressed to me and not an SOF?" — the same property expressed as a statement about history rather than about state.

That distinction is the whole value of the model. A bug in a state machine can hide perfectly inside an identical state machine; it cannot hide inside a different formulation of the same claim.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   design:  tx_valid <= armed            (a state question)
   shadow:  expect = p_acc              (a history question)

            where p_acc is recomputed each cycle as
            "the previous token named me and was not SOF"

   Same property. Different machine. Either one being
   wrong shows up as a disagreement.

On top of that, the bench keeps a counter called n_unsolicited: every cycle in which the device transmitted without a preceding accepted token. It is asserted against zero on every step and printed at the end, because "it never happened" is a claim that needs a number behind it — and because a run in which the situation never arose would otherwise report PASS while proving nothing.

Verilog-2005 testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for usb_token_gate.
//
//  The shadow model is deliberately NOT a copy of the design's FSM.
//  The design asks "am I armed?"; the shadow asks "was the token in
//  the PREVIOUS cycle addressed to me and not an SOF?" -- the same
//  property expressed as a statement about history rather than about
//  state, so that a defect in the state machine cannot hide inside an
//  identical state machine.
// =====================================================================
`timescale 1ns/1ps
module tb_tg_v;

  localparam [6:0] DEV_ADDR = 7'h2A;
  localparam       N_EP     = 4;

  localparam [1:0] T_OUT   = 2'd0, T_IN = 2'd1, T_SOF = 2'd2, T_SETUP = 2'd3;
  localparam [2:0] R_NONE  = 3'd0, R_DATA = 3'd1, R_NAK = 3'd2,
                   R_STALL = 3'd3, R_ACK  = 3'd4;

  reg              clk = 1'b0, rst_n = 1'b0;
  reg              tok_valid = 1'b0;
  reg  [1:0]       tok_pid   = T_OUT;
  reg  [6:0]       tok_addr  = 7'd0;
  reg  [1:0]       tok_ep    = 2'd0;
  reg  [N_EP-1:0]  data_avail = {N_EP{1'b0}};
  reg  [N_EP-1:0]  halted     = {N_EP{1'b0}};

  wire             tx_valid;
  wire [2:0]       tx_pid;
  wire [1:0]       tx_ep;
  wire [31:0]      n_resp, n_ignored, n_nak, n_stall, n_superseded;

  usb_token_gate #(.DEV_ADDR(DEV_ADDR), .N_EP(N_EP)) dut (
    .clk(clk), .rst_n(rst_n),
    .tok_valid(tok_valid), .tok_pid(tok_pid),
    .tok_addr(tok_addr), .tok_ep(tok_ep),
    .data_avail(data_avail), .halted(halted),
    .tx_valid(tx_valid), .tx_pid(tx_pid), .tx_ep(tx_ep),
    .n_resp(n_resp), .n_ignored(n_ignored), .n_nak(n_nak),
    .n_stall(n_stall), .n_superseded(n_superseded)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0, steps = 0;

  // ---- the shadow, as history ----
  reg        p_acc;          // the previous cycle's token was for us
  reg [1:0]  p_ep, p_pid;
  reg [31:0] s_resp, s_ign, s_nak, s_stall, s_sup;

  // ---- the headline counter ----
  //
  // Every cycle in which the device transmitted WITHOUT a preceding
  // accepted token. It is checked against zero on every single step
  // and reported at the end, because "it never happened" is a claim
  // that needs a number behind it.
  integer n_unsolicited = 0;

  // ---- exhaustive reach over (pid, ours, ep, avail, halt) ----
  reg reach [0:127];
  integer ri, n_reach;

  task ck(input cond, input [255:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step=%0d: %0s", $time, steps, what);
      end
    end
  endtask

  // ---------------------------------------------------------------
  //  One bus cycle: drive, clock, check, then advance the shadow.
  // ---------------------------------------------------------------
  task step(input tv, input [1:0] tp, input [6:0] ta, input [1:0] te,
            input [N_EP-1:0] da, input [N_EP-1:0] hl);
    reg       e_valid;
    reg [2:0] e_pid;
    reg [1:0] e_ep;
    reg       acc;
    begin
      tok_valid  = tv;  tok_pid = tp;  tok_addr = ta;  tok_ep = te;
      data_avail = da;  halted  = hl;

      // ---- what the device is allowed to do on THIS edge ----
      //
      // Entirely a function of the PREVIOUS cycle's token plus this
      // cycle's device state. Nothing about the current token can
      // create a right to transmit now.
      e_valid = p_acc;
      e_ep    = p_ep;
      e_pid   = R_NONE;
      if (p_acc) begin
        if (p_pid == T_IN) begin
          if      (hl[p_ep]) e_pid = R_STALL;
          else if (da[p_ep]) e_pid = R_DATA;
          else               e_pid = R_NAK;
        end else begin
          if (hl[p_ep] && (p_pid != T_SETUP)) e_pid = R_STALL;
          else                                e_pid = R_ACK;
        end
      end

      // shadow counters, advanced before the edge so they can be
      // compared against the design's on the same cycle
      if (p_acc) begin
        s_resp = s_resp + 1;
        if (e_pid == R_NAK)   s_nak   = s_nak   + 1;
        if (e_pid == R_STALL) s_stall = s_stall + 1;
      end
      acc = tv && (ta == DEV_ADDR) && (tp != T_SOF);
      if (tv) begin
        if (p_acc) s_sup = s_sup + 1;
        if (!acc)  s_ign = s_ign + 1;
      end

      @(posedge clk);
      #1;
      steps = steps + 1;

      // ---- PROPERTY 1: no transmission without permission ----
      if (tx_valid && !e_valid) n_unsolicited = n_unsolicited + 1;
      ck(tx_valid === e_valid, "tx_valid disagrees with the token history");

      // ---- PROPERTY 2: the right answer, on the right endpoint ----
      if (e_valid) begin
        ck(tx_pid === e_pid, "wrong response PID");
        ck(tx_ep  === e_ep,  "response on the wrong endpoint");
      end else begin
        ck(tx_pid === R_NONE, "a PID was driven while silent");
      end

      // ---- PROPERTY 3: the counters agree with an independent tally ----
      ck(n_resp       === s_resp,  "response count disagrees");
      ck(n_ignored    === s_ign,   "ignored-token count disagrees");
      ck(n_nak        === s_nak,   "NAK count disagrees");
      ck(n_stall      === s_stall, "STALL count disagrees");
      ck(n_superseded === s_sup,   "superseded count disagrees");

      // ---- PROPERTY 4: the headline, asserted every cycle ----
      ck(n_unsolicited == 0, "the device transmitted unsolicited");

      // advance the history
      p_acc = acc;
      if (acc) begin p_ep = te; p_pid = tp; end
    end
  endtask

  task reset_dut;
    begin
      rst_n = 1'b0;
      tok_valid = 1'b0; data_avail = {N_EP{1'b0}}; halted = {N_EP{1'b0}};
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      p_acc = 1'b0; p_ep = 2'd0; p_pid = T_OUT;
      s_resp = 0; s_ign = 0; s_nak = 0; s_stall = 0; s_sup = 0;
      @(posedge clk); #1;
    end
  endtask

  integer pi, oi, ei, ai, hi, k, seed;
  reg [N_EP-1:0] dv, hv;
  reg [6:0] addr;

  initial begin
    for (ri = 0; ri < 128; ri = ri + 1) reach[ri] = 1'b0;
    seed = 32'd27001;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every token against every
    //  device state. 4 PIDs x ours/not x 4 endpoints x avail x halt.
    // =============================================================
    reset_dut;
    for (pi = 0; pi < 4; pi = pi + 1)
    for (oi = 0; oi < 2; oi = oi + 1)
    for (ei = 0; ei < 4; ei = ei + 1)
    for (ai = 0; ai < 2; ai = ai + 1)
    for (hi = 0; hi < 2; hi = hi + 1) begin
      dv   = ai ? (1 << ei) : {N_EP{1'b0}};
      hv   = hi ? (1 << ei) : {N_EP{1'b0}};
      addr = oi ? DEV_ADDR : 7'h55;

      // the token ...
      step(1'b1, pi[1:0], addr, ei[1:0], dv, hv);
      // ... and the cycle in which the answer is due
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, hv);
      // ... and one more, in which there must be silence again
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, hv);

      ri = (pi << 5) | (oi << 4) | (ei << 2) | (ai << 1) | hi;
      reach[ri] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED) -- the device wants to talk and may not.
    //
    //  Every data_avail pattern, with no token on the bus at all.
    //  This is the 90-second answer as an experiment: a device with
    //  sixteen different kinds of urgent news and no way to deliver
    //  any of it.
    // =============================================================
    reset_dut;
    for (k = 0; k < 16; k = k + 1) begin
      dv = k[3:0];
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, {N_EP{1'b0}});
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, {N_EP{1'b0}});
      step(1'b0, T_IN,  7'd0, 2'd0, dv, {N_EP{1'b0}});   // pid set, no valid
    end

    // =============================================================
    //  PHASE 2b (DIRECTED) -- NAK is a loop, not an answer.
    //
    //  An empty endpoint must NAK EVERY time it is polled, not once,
    //  and must switch to DATA the instant it has something. That is
    //  the entire mechanism by which a device which cannot initiate
    //  is nonetheless able to say "not yet" -- and testing it once
    //  per endpoint tests nothing, because a single NAK is also what
    //  a design that NAKs permanently would produce.
    // =============================================================
    reset_dut;
    for (ei = 0; ei < 4; ei = ei + 1) begin
      for (k = 0; k < 6; k = k + 1) begin
        step(1'b1, T_IN, DEV_ADDR, ei[1:0], 4'h0, 4'h0);
        step(1'b0, T_OUT, 7'd0, 2'd0, 4'h0, 4'h0);
      end
      step(1'b1, T_IN, DEV_ADDR, ei[1:0], (4'h1 << ei), 4'h0);
      step(1'b0, T_OUT, 7'd0, 2'd0, (4'h1 << ei), 4'h0);
      for (k = 0; k < 3; k = k + 1) begin
        step(1'b1, T_IN, DEV_ADDR, ei[1:0], 4'h0, 4'h0);
        step(1'b0, T_OUT, 7'd0, 2'd0, 4'h0, 4'h0);
      end
    end

    // =============================================================
    //  PHASE 3 (DIRECTED) -- a superseded token is never answered.
    //
    //  Back-to-back tokens: the host asked, changed its mind, and
    //  asked something else. A device that answers the first is
    //  transmitting into the slot the host gave to somebody else.
    //  Exhaustive over (first ep x second ep x second is-ours).
    // =============================================================
    reset_dut;
    for (ei = 0; ei < 4; ei = ei + 1)
    for (k = 0; k < 4; k = k + 1)
    for (oi = 0; oi < 2; oi = oi + 1) begin
      step(1'b1, T_IN, DEV_ADDR, ei[1:0], 4'hF, 4'h0);
      step(1'b1, T_IN, oi ? DEV_ADDR : 7'h55, k[1:0], 4'hF, 4'h0);
      step(1'b0, T_OUT, 7'd0, 2'd0, 4'hF, 4'h0);
      step(1'b0, T_OUT, 7'd0, 2'd0, 4'hF, 4'h0);
    end

    // =============================================================
    //  PHASE 4 (RANDOM) -- a busy bus with four other devices.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 40000; k = k + 1) begin
      // A token on most cycles, because a real bus is busy; a quarter
      // of them ours, the rest addressed to the other devices whose
      // traffic this device must sit through in silence.
      if (($random(seed) % 100) < 70)
        step(1'b1,
             ($random(seed) % 4),
             (($random(seed) % 4) == 0) ? DEV_ADDR : (($random(seed) & 7'h7F) | 7'h01),
             ($random(seed) % 4),
             ($random(seed) & 4'hF),
             (($random(seed) % 8) == 0) ? ($random(seed) & 4'hF) : 4'h0);
      else
        step(1'b0, T_OUT, 7'd0, 2'd0, ($random(seed) & 4'hF), 4'h0);
    end
`endif

    n_reach = 0;
    for (ri = 0; ri < 128; ri = ri + 1) if (reach[ri]) n_reach = n_reach + 1;

    $display("steps=%0d checks=%0d reach=%0d/128 errors=%0d",
             steps, checks, n_reach, errors);
    $display("[bus] responses=%0d ignored=%0d nak=%0d stall=%0d superseded=%0d",
             n_resp, n_ignored, n_nak, n_stall, n_superseded);
    $display("[the whole point] unsolicited transmissions = %0d", n_unsolicited);
    if (n_reach != 128) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for usb_token_gate.
//
//  The shadow model is deliberately NOT a copy of the design's FSM.
//  The design asks "am I armed?"; the shadow asks "was the token in
//  the PREVIOUS cycle addressed to me and not an SOF?" -- the same
//  property expressed as a statement about history rather than about
//  state, so that a defect in the state machine cannot hide inside an
//  identical state machine.
// =====================================================================
`timescale 1ns/1ps
module tb_tg_sv;
  import tg_pkg::*;

  localparam [6:0] DEV_ADDR = 7'h2A;
  localparam       N_EP     = 4;


  logic            clk = 1'b0, rst_n = 1'b0;
  logic            tok_valid = 1'b0;
  logic[1:0]       tok_pid   = T_OUT;
  logic[6:0]       tok_addr  = 7'd0;
  logic[1:0]       tok_ep    = 2'd0;
  logic[N_EP-1:0]  data_avail = {N_EP{1'b0}};
  logic[N_EP-1:0]  halted     = {N_EP{1'b0}};

  logic             tx_valid;
  logic [2:0]       tx_pid;
  logic [1:0]       tx_ep;
  logic [31:0]      n_resp, n_ignored, n_nak, n_stall, n_superseded;

  usb_token_gate #(.DEV_ADDR(DEV_ADDR), .N_EP(N_EP)) dut (
    .clk(clk), .rst_n(rst_n),
    .tok_valid(tok_valid), .tok_pid(tok_pid),
    .tok_addr(tok_addr), .tok_ep(tok_ep),
    .data_avail(data_avail), .halted(halted),
    .tx_valid(tx_valid), .tx_pid(tx_pid), .tx_ep(tx_ep),
    .n_resp(n_resp), .n_ignored(n_ignored), .n_nak(n_nak),
    .n_stall(n_stall), .n_superseded(n_superseded)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0, steps = 0;

  // ---- the shadow, as history ----
  logic      p_acc;          // the previous cycle's token was for us
  logic [1:0]  p_ep, p_pid;
  logic [31:0] s_resp, s_ign, s_nak, s_stall, s_sup;

  // ---- the headline counter ----
  //
  // Every cycle in which the device transmitted WITHOUT a preceding
  // accepted token. It is checked against zero on every single step
  // and reported at the end, because "it never happened" is a claim
  // that needs a number behind it.
  integer n_unsolicited = 0;

  // ---- exhaustive reach over (pid, ours, ep, avail, halt) ----
  logic reach [0:127];
  integer ri, n_reach;

  task ck(input cond, input logic [255:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step=%0d: %0s", $time, steps, what);
      end
    end
  endtask

  // ---------------------------------------------------------------
  //  One bus cycle: drive, clock, check, then advance the shadow.
  // ---------------------------------------------------------------
  task step(input tv, input [1:0] tp, input [6:0] ta, input [1:0] te,
            input [N_EP-1:0] da, input [N_EP-1:0] hl);
    logic     e_valid;
    logic [2:0] e_pid;
    logic [1:0] e_ep;
    logic     acc;
    begin
      tok_valid  = tv;  tok_pid = tp;  tok_addr = ta;  tok_ep = te;
      data_avail = da;  halted  = hl;

      // ---- what the device is allowed to do on THIS edge ----
      //
      // Entirely a function of the PREVIOUS cycle's token plus this
      // cycle's device state. Nothing about the current token can
      // create a right to transmit now.
      e_valid = p_acc;
      e_ep    = p_ep;
      e_pid   = R_NONE;
      if (p_acc) begin
        if (p_pid == T_IN) begin
          if      (hl[p_ep]) e_pid = R_STALL;
          else if (da[p_ep]) e_pid = R_DATA;
          else               e_pid = R_NAK;
        end else begin
          if (hl[p_ep] && (p_pid != T_SETUP)) e_pid = R_STALL;
          else                                e_pid = R_ACK;
        end
      end

      // shadow counters, advanced before the edge so they can be
      // compared against the design's on the same cycle
      if (p_acc) begin
        s_resp = s_resp + 1;
        if (e_pid == R_NAK)   s_nak   = s_nak   + 1;
        if (e_pid == R_STALL) s_stall = s_stall + 1;
      end
      acc = tv && (ta == DEV_ADDR) && (tp != T_SOF);
      if (tv) begin
        if (p_acc) s_sup = s_sup + 1;
        if (!acc)  s_ign = s_ign + 1;
      end

      @(posedge clk);
      #1;
      steps = steps + 1;

      // ---- PROPERTY 1: no transmission without permission ----
      if (tx_valid && !e_valid) n_unsolicited = n_unsolicited + 1;
      ck(tx_valid === e_valid, "tx_valid disagrees with the token history");

      // ---- PROPERTY 2: the right answer, on the right endpoint ----
      if (e_valid) begin
        ck(tx_pid === e_pid, "wrong response PID");
        ck(tx_ep  === e_ep,  "response on the wrong endpoint");
      end else begin
        ck(tx_pid === R_NONE, "a PID was driven while silent");
      end

      // ---- PROPERTY 3: the counters agree with an independent tally ----
      ck(n_resp       === s_resp,  "response count disagrees");
      ck(n_ignored    === s_ign,   "ignored-token count disagrees");
      ck(n_nak        === s_nak,   "NAK count disagrees");
      ck(n_stall      === s_stall, "STALL count disagrees");
      ck(n_superseded === s_sup,   "superseded count disagrees");

      // ---- PROPERTY 4: the headline, asserted every cycle ----
      ck(n_unsolicited == 0, "the device transmitted unsolicited");

      // advance the history
      p_acc = acc;
      if (acc) begin p_ep = te; p_pid = tp; end
    end
  endtask

  task reset_dut;
    begin
      rst_n = 1'b0;
      tok_valid = 1'b0; data_avail = {N_EP{1'b0}}; halted = {N_EP{1'b0}};
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      p_acc = 1'b0; p_ep = 2'd0; p_pid = T_OUT;
      s_resp = 0; s_ign = 0; s_nak = 0; s_stall = 0; s_sup = 0;
      @(posedge clk); #1;
    end
  endtask

  integer pi, oi, ei, ai, hi, k, seed;
  logic [N_EP-1:0] dv, hv;
  logic [6:0] addr;

  initial begin
    for (ri = 0; ri < 128; ri = ri + 1) reach[ri] = 1'b0;
    seed = 32'd27001;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every token against every
    //  device state. 4 PIDs x ours/not x 4 endpoints x avail x halt.
    // =============================================================
    reset_dut;
    for (pi = 0; pi < 4; pi = pi + 1)
    for (oi = 0; oi < 2; oi = oi + 1)
    for (ei = 0; ei < 4; ei = ei + 1)
    for (ai = 0; ai < 2; ai = ai + 1)
    for (hi = 0; hi < 2; hi = hi + 1) begin
      dv   = ai ? (1 << ei) : {N_EP{1'b0}};
      hv   = hi ? (1 << ei) : {N_EP{1'b0}};
      addr = oi ? DEV_ADDR : 7'h55;

      // the token ...
      step(1'b1, pi[1:0], addr, ei[1:0], dv, hv);
      // ... and the cycle in which the answer is due
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, hv);
      // ... and one more, in which there must be silence again
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, hv);

      ri = (pi << 5) | (oi << 4) | (ei << 2) | (ai << 1) | hi;
      reach[ri] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED) -- the device wants to talk and may not.
    //
    //  Every data_avail pattern, with no token on the bus at all.
    //  This is the 90-second answer as an experiment: a device with
    //  sixteen different kinds of urgent news and no way to deliver
    //  any of it.
    // =============================================================
    reset_dut;
    for (k = 0; k < 16; k = k + 1) begin
      dv = k[3:0];
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, {N_EP{1'b0}});
      step(1'b0, T_OUT, 7'd0, 2'd0, dv, {N_EP{1'b0}});
      step(1'b0, T_IN,  7'd0, 2'd0, dv, {N_EP{1'b0}});   // pid set, no valid
    end

    // =============================================================
    //  PHASE 2b (DIRECTED) -- NAK is a loop, not an answer.
    //
    //  An empty endpoint must NAK EVERY time it is polled, not once,
    //  and must switch to DATA the instant it has something. That is
    //  the entire mechanism by which a device which cannot initiate
    //  is nonetheless able to say "not yet" -- and testing it once
    //  per endpoint tests nothing, because a single NAK is also what
    //  a design that NAKs permanently would produce.
    // =============================================================
    reset_dut;
    for (ei = 0; ei < 4; ei = ei + 1) begin
      for (k = 0; k < 6; k = k + 1) begin
        step(1'b1, T_IN, DEV_ADDR, ei[1:0], 4'h0, 4'h0);
        step(1'b0, T_OUT, 7'd0, 2'd0, 4'h0, 4'h0);
      end
      step(1'b1, T_IN, DEV_ADDR, ei[1:0], (4'h1 << ei), 4'h0);
      step(1'b0, T_OUT, 7'd0, 2'd0, (4'h1 << ei), 4'h0);
      for (k = 0; k < 3; k = k + 1) begin
        step(1'b1, T_IN, DEV_ADDR, ei[1:0], 4'h0, 4'h0);
        step(1'b0, T_OUT, 7'd0, 2'd0, 4'h0, 4'h0);
      end
    end

    // =============================================================
    //  PHASE 3 (DIRECTED) -- a superseded token is never answered.
    //
    //  Back-to-back tokens: the host asked, changed its mind, and
    //  asked something else. A device that answers the first is
    //  transmitting into the slot the host gave to somebody else.
    //  Exhaustive over (first ep x second ep x second is-ours).
    // =============================================================
    reset_dut;
    for (ei = 0; ei < 4; ei = ei + 1)
    for (k = 0; k < 4; k = k + 1)
    for (oi = 0; oi < 2; oi = oi + 1) begin
      step(1'b1, T_IN, DEV_ADDR, ei[1:0], 4'hF, 4'h0);
      step(1'b1, T_IN, oi ? DEV_ADDR : 7'h55, k[1:0], 4'hF, 4'h0);
      step(1'b0, T_OUT, 7'd0, 2'd0, 4'hF, 4'h0);
      step(1'b0, T_OUT, 7'd0, 2'd0, 4'hF, 4'h0);
    end

    // =============================================================
    //  PHASE 4 (RANDOM) -- a busy bus with four other devices.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 40000; k = k + 1) begin
      // A token on most cycles, because a real bus is busy; a quarter
      // of them ours, the rest addressed to the other devices whose
      // traffic this device must sit through in silence.
      if (($random(seed) % 100) < 70)
        step(1'b1,
             ($random(seed) % 4),
             (($random(seed) % 4) == 0) ? DEV_ADDR : (($random(seed) & 7'h7F) | 7'h01),
             ($random(seed) % 4),
             ($random(seed) & 4'hF),
             (($random(seed) % 8) == 0) ? ($random(seed) & 4'hF) : 4'h0);
      else
        step(1'b0, T_OUT, 7'd0, 2'd0, ($random(seed) & 4'hF), 4'h0);
    end
`endif

    n_reach = 0;
    for (ri = 0; ri < 128; ri = ri + 1) if (reach[ri]) n_reach = n_reach + 1;

    $display("steps=%0d checks=%0d reach=%0d/128 errors=%0d",
             steps, checks, n_reach, errors);
    $display("[bus] responses=%0d ignored=%0d nak=%0d stall=%0d superseded=%0d",
             n_resp, n_ignored, n_nak, n_stall, n_superseded);
    $display("[the whole point] unsolicited transmissions = %0d", n_unsolicited);
    if (n_reach != 128) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

VHDL-2008 testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  Testbench for usb_token_gate (VHDL-2008).
--
--  Same shadow model as the Verilog bench and the same phases, with an
--  independent pseudo-random source -- so the random columns of the
--  mutation table are genuinely a second opinion rather than the same
--  stimulus compiled twice.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.tg_pkg.all;

entity tb_tg_vhdl is
  -- VHDL has no preprocessor, so the directed/random split is an
  -- elaboration-time generic: nvc -e -gDIRECTED_ONLY=true
  generic (DIRECTED_ONLY : boolean := false);
end entity;

architecture sim of tb_tg_vhdl is
  constant DEV_ADDR : std_logic_vector(6 downto 0) := "0101010";
  constant N_EP     : natural := 4;

  signal clk        : std_logic := '0';
  signal rst_n      : std_logic := '0';
  signal tok_valid  : std_logic := '0';
  signal tok_pid    : std_logic_vector(1 downto 0) := TOK_OUT;
  signal tok_addr   : std_logic_vector(6 downto 0) := (others => '0');
  signal tok_ep     : std_logic_vector(1 downto 0) := "00";
  signal data_avail : std_logic_vector(N_EP-1 downto 0) := (others => '0');
  signal halted     : std_logic_vector(N_EP-1 downto 0) := (others => '0');

  signal tx_valid   : std_logic;
  signal tx_pid     : std_logic_vector(2 downto 0);
  signal tx_ep      : std_logic_vector(1 downto 0);
  signal n_resp, n_ignored, n_nak, n_stall, n_superseded
                    : std_logic_vector(31 downto 0);

  signal done : boolean := false;
begin

  dut : entity work.usb_token_gate
    generic map (DEV_ADDR => DEV_ADDR, N_EP => N_EP)
    port map (
      clk => clk, rst_n => rst_n,
      tok_valid => tok_valid, tok_pid => tok_pid,
      tok_addr => tok_addr, tok_ep => tok_ep,
      data_avail => data_avail, halted => halted,
      tx_valid => tx_valid, tx_pid => tx_pid, tx_ep => tx_ep,
      n_resp => n_resp, n_ignored => n_ignored, n_nak => n_nak,
      n_stall => n_stall, n_superseded => n_superseded);

  clk <= not clk after 5 ns when not done else '0';

  stim : process
    variable errors   : natural := 0;
    variable checks   : natural := 0;
    variable steps    : natural := 0;
    variable unsol    : natural := 0;

    variable p_acc    : std_logic := '0';
    variable p_ep     : std_logic_vector(1 downto 0) := "00";
    variable p_pid    : std_logic_vector(1 downto 0) := TOK_OUT;
    variable s_resp, s_ign, s_nak, s_stall, s_sup : natural := 0;

    variable reach    : std_logic_vector(0 to 127) := (others => '0');
    variable n_reach  : natural := 0;

    variable rnd      : unsigned(31 downto 0) := x"0006975B";
    variable ln       : line;

    -- VHDL wants every subprogram declared before it is used, so the
    -- order of these three is load-bearing.
    procedure ck(cond : boolean; what : string) is
    begin
      checks := checks + 1;
      if not cond then
        errors := errors + 1;
        if errors <= 20 then
          write(ln, string'("  ERROR step=") & integer'image(steps)
                & string'(": ") & what);
          writeline(output, ln);
        end if;
      end if;
    end procedure;

    impure function nxt return natural is
    begin
      -- a 32-bit xorshift: unrelated to Icarus's generator, which is
      -- the entire reason the VHDL random column means something
      rnd := rnd xor (rnd sll 13);
      rnd := rnd xor (rnd srl 17);
      rnd := rnd xor (rnd sll 5);
      return to_integer(rnd(14 downto 0));
    end function;

    procedure step(tv : std_logic;
                   tp : std_logic_vector(1 downto 0);
                   ta : std_logic_vector(6 downto 0);
                   te : std_logic_vector(1 downto 0);
                   da : std_logic_vector(N_EP-1 downto 0);
                   hl : std_logic_vector(N_EP-1 downto 0)) is
      variable e_valid : std_logic;
      variable e_pid   : std_logic_vector(2 downto 0);
      variable e_ep    : std_logic_vector(1 downto 0);
      variable acc     : std_logic;
      variable pe      : natural;
    begin
      tok_valid  <= tv;  tok_pid <= tp;  tok_addr <= ta;  tok_ep <= te;
      data_avail <= da;  halted  <= hl;

      e_valid := p_acc;
      e_ep    := p_ep;
      e_pid   := RSP_NONE;
      pe      := to_integer(unsigned(p_ep));
      if p_acc = '1' then
        if p_pid = TOK_IN then
          if    hl(pe) = '1' then e_pid := RSP_STALL;
          elsif da(pe) = '1' then e_pid := RSP_DATA;
          else                    e_pid := RSP_NAK;
          end if;
        else
          if hl(pe) = '1' and p_pid /= TOK_SETUP then e_pid := RSP_STALL;
          else                                        e_pid := RSP_ACK;
          end if;
        end if;
        s_resp := s_resp + 1;
        if e_pid = RSP_NAK   then s_nak   := s_nak   + 1; end if;
        if e_pid = RSP_STALL then s_stall := s_stall + 1; end if;
      end if;

      acc := '0';
      if tv = '1' and ta = DEV_ADDR and tp /= TOK_SOF then acc := '1'; end if;
      if tv = '1' then
        if p_acc = '1' then s_sup := s_sup + 1; end if;
        if acc = '0'   then s_ign := s_ign + 1; end if;
      end if;

      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;

      if tx_valid = '1' and e_valid = '0' then unsol := unsol + 1; end if;
      ck(tx_valid = e_valid, "tx_valid disagrees with the token history");

      if e_valid = '1' then
        ck(tx_pid = e_pid, "wrong response PID");
        ck(tx_ep  = e_ep,  "response on the wrong endpoint");
      else
        ck(tx_pid = RSP_NONE, "a PID was driven while silent");
      end if;

      ck(to_integer(unsigned(n_resp))       = s_resp,  "response count disagrees");
      ck(to_integer(unsigned(n_ignored))    = s_ign,   "ignored-token count disagrees");
      ck(to_integer(unsigned(n_nak))        = s_nak,   "NAK count disagrees");
      ck(to_integer(unsigned(n_stall))      = s_stall, "STALL count disagrees");
      ck(to_integer(unsigned(n_superseded)) = s_sup,   "superseded count disagrees");
      ck(unsol = 0, "the device transmitted unsolicited");

      p_acc := acc;
      if acc = '1' then p_ep := te; p_pid := tp; end if;
    end procedure;

    procedure reset_dut is
    begin
      rst_n <= '0';
      tok_valid <= '0';
      data_avail <= (others => '0');
      halted <= (others => '0');
      wait until rising_edge(clk);
      wait until rising_edge(clk);
      rst_n <= '1';
      p_acc := '0'; p_ep := "00"; p_pid := TOK_OUT;
      s_resp := 0; s_ign := 0; s_nak := 0; s_stall := 0; s_sup := 0;
      wait until rising_edge(clk);
      wait for 1 ns;
    end procedure;

    variable dv, hv : std_logic_vector(N_EP-1 downto 0);
    variable addr   : std_logic_vector(6 downto 0);
    variable ri     : natural;
  begin
    -- PHASE 1 (DIRECTED, EXHAUSTIVE)
    reset_dut;
    for pi in 0 to 3 loop
      for oi in 0 to 1 loop
        for ei in 0 to 3 loop
          for ai in 0 to 1 loop
            for hi in 0 to 1 loop
              dv := (others => '0');
              hv := (others => '0');
              if ai = 1 then dv(ei) := '1'; end if;
              if hi = 1 then hv(ei) := '1'; end if;
              if oi = 1 then addr := DEV_ADDR; else addr := "1010101"; end if;

              step('1', std_logic_vector(to_unsigned(pi, 2)), addr,
                   std_logic_vector(to_unsigned(ei, 2)), dv, hv);
              step('0', TOK_OUT, "0000000", "00", dv, hv);
              step('0', TOK_OUT, "0000000", "00", dv, hv);

              ri := pi*32 + oi*16 + ei*4 + ai*2 + hi;
              reach(ri) := '1';
            end loop;
          end loop;
        end loop;
      end loop;
    end loop;

    -- PHASE 2 (DIRECTED) -- the device wants to talk and may not
    reset_dut;
    for k in 0 to 15 loop
      dv := std_logic_vector(to_unsigned(k, N_EP));
      step('0', TOK_OUT, "0000000", "00", dv, (others => '0'));
      step('0', TOK_OUT, "0000000", "00", dv, (others => '0'));
      step('0', TOK_IN,  "0000000", "00", dv, (others => '0'));
    end loop;

    -- PHASE 2b (DIRECTED) -- NAK is a loop, not an answer
    reset_dut;
    for ei in 0 to 3 loop
      for k in 0 to 5 loop
        step('1', TOK_IN, DEV_ADDR, std_logic_vector(to_unsigned(ei, 2)),
             "0000", "0000");
        step('0', TOK_OUT, "0000000", "00", "0000", "0000");
      end loop;
      dv := (others => '0');
      dv(ei) := '1';
      step('1', TOK_IN, DEV_ADDR, std_logic_vector(to_unsigned(ei, 2)),
           dv, "0000");
      step('0', TOK_OUT, "0000000", "00", dv, "0000");
      for k in 0 to 2 loop
        step('1', TOK_IN, DEV_ADDR, std_logic_vector(to_unsigned(ei, 2)),
             "0000", "0000");
        step('0', TOK_OUT, "0000000", "00", "0000", "0000");
      end loop;
    end loop;

    -- PHASE 3 (DIRECTED) -- a superseded token is never answered
    reset_dut;
    for ei in 0 to 3 loop
      for k in 0 to 3 loop
        for oi in 0 to 1 loop
          step('1', TOK_IN, DEV_ADDR, std_logic_vector(to_unsigned(ei, 2)),
               "1111", "0000");
          if oi = 1 then addr := DEV_ADDR; else addr := "1010101"; end if;
          step('1', TOK_IN, addr, std_logic_vector(to_unsigned(k, 2)),
               "1111", "0000");
          step('0', TOK_OUT, "0000000", "00", "1111", "0000");
          step('0', TOK_OUT, "0000000", "00", "1111", "0000");
        end loop;
      end loop;
    end loop;

    -- PHASE 4 (RANDOM)
    if not DIRECTED_ONLY then
    reset_dut;
    for k in 0 to 39999 loop
      if (nxt mod 100) < 70 then
        if (nxt mod 4) = 0 then addr := DEV_ADDR;
        else addr := std_logic_vector(to_unsigned((nxt mod 127) + 1, 7));
        end if;
        dv := std_logic_vector(to_unsigned(nxt mod 16, N_EP));
        if (nxt mod 8) = 0 then
          hv := std_logic_vector(to_unsigned(nxt mod 16, N_EP));
        else
          hv := (others => '0');
        end if;
        step('1', std_logic_vector(to_unsigned(nxt mod 4, 2)), addr,
             std_logic_vector(to_unsigned(nxt mod 4, 2)), dv, hv);
      else
        dv := std_logic_vector(to_unsigned(nxt mod 16, N_EP));
        step('0', TOK_OUT, "0000000", "00", dv, "0000");
      end if;
    end loop;
    end if;

    n_reach := 0;
    for i in 0 to 127 loop
      if reach(i) = '1' then n_reach := n_reach + 1; end if;
    end loop;

    write(ln, string'("steps=") & integer'image(steps)
          & string'(" checks=") & integer'image(checks)
          & string'(" reach=") & integer'image(n_reach) & string'("/128")
          & string'(" errors=") & integer'image(errors));
    writeline(output, ln);
    write(ln, string'("[bus] responses=") & integer'image(s_resp)
          & string'(" ignored=") & integer'image(s_ign)
          & string'(" nak=") & integer'image(s_nak)
          & string'(" stall=") & integer'image(s_stall)
          & string'(" superseded=") & integer'image(s_sup));
    writeline(output, ln);
    write(ln, string'("[the whole point] unsolicited transmissions = ")
          & integer'image(unsol));
    writeline(output, ln);
    if n_reach /= 128 then
      write(ln, string'("FAIL: exhaustive sweep incomplete"));
      writeline(output, ln);
      errors := errors + 1;
    end if;
    if errors = 0 then
      write(ln, string'("PASS: 0 errors in ") & integer'image(checks)
            & string'(" checks"));
    else
      write(ln, string'("FAIL: ") & integer'image(errors)
            & string'(" errors in ") & integer'image(checks) & string'(" checks"));
    end if;
    writeline(output, ln);

    done <= true;
    wait;
  end process;

end architecture;

10. Exhaustive Verification

MeasureVerilogSystemVerilogVHDL
(PID × ours × endpoint × data × halted) reached128 / 128128 / 128128 / 128
NAK-loop polls swept40 / 4040 / 4040 / 40
supersede scenarios swept32 / 3232 / 3232 / 32
Steps406404064040640
Checks executed331561331561330578
responses issued630563055322
tokens correctly ignored277812778122688
NAKs10531053862
STALLs221221156
superseded tokens534353433715
unsolicited transmissions000
ResultPASSPASSPASS

The exhaustive sweep is every combination of what the host asked and what the device wanted: 4 token types × addressed-to-us or not × 4 endpoints × data available or not × halted or not. All 128, each followed by the cycle in which an answer is due and one more in which there must be silence again.

11. Mutation Testing

#MutationVerilogSysVerVHDL
A1the address check is dropped — the device answers for everybody280793280793273647
A4a token for another device leaves our permission standing228331228331214051
A2SOF is answered205094205094204305
A3the device transmits when it has data — no token needed145085145085146685
A6a halted endpoint answers normally instead of stalling794147941479190
A7SETUP is stalled when the endpoint is halted390603906038145
A5an empty endpoint sends DATA instead of NAK10931093902
—unmutated baseline000

All seven die in all three languages.

A3 is the mutation this chapter exists for — it is the ninety-second answer being false. Nine lines that let the device transmit because it felt like it. It dies 145,085 times, and every one of those is the bench noticing a byte on the bus that the host never asked for.

Directed against random

#All phasesDirected onlyRandom
A12807932454278339
A2205094858204236
A31450851847143238
A4228331284228047
A51093401053
A67941448078934
A7390605239008

Every one is killed by directed stimulus alone. The random phase dominates the totals — it is 40,000 steps against roughly 640 directed ones — but no mutation depends on it.

12. Two Findings the Mutations Produced

A5's directed score was 4, and 4 is luck

The first run of this matrix gave A5 a directed score of four. Four is not a result. It is the number of times a narrow situation happens to line up with a check, and a different seed would have given 2 or 7.

The cause was arithmetic rather than mysterious. For A5 to be observable the bench needs an IN token, addressed to us, on a non-halted endpoint with no data — and the exhaustive sweep contains exactly four such cells, one per endpoint. Each produced exactly one failing check, because A5 leaves the NAK counter correct and only the PID is wrong, so nothing diverges cumulatively.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   A5 directed = 4 = the number of scenarios
                     x 1 check each

   A score equal to the scenario count means every
   scenario is checked ONCE. That is cornered, not killed.

The cure was not more random cycles. It was to ask what property was actually being claimed and then drive it over its domain. The claim is not "an empty endpoint NAKs"; it is "NAK is neither sticky nor terminal" — an empty endpoint NAKs every poll, and switches to DATA the instant it has something. That takes a loop, not a single token:

six polls while empty, one poll with data, three more while empty — per endpoint, over all four. A5 went from 4 to 40, and the suite gained a property it genuinely did not have before.

A6's SystemVerilog column read 102 against 79414

The first three-language run put A6 at 79,414 in Verilog, 79,190 in VHDL, and 102 in SystemVerilog. A 780× spread in one language.

The rule for this shape is that an out-of-line column is usually the mutant, not the testbench — and it was again. The Verilog mutation disables the halted test in a block that controls both the response PID and the STALL counter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   Verilog A6:  if (1'b0) begin           <- was halted[ep]
                  tx_pid_r <= R_STALL;
                  stall_r  <= stall_r + 1;   <- ALSO disabled
                end else if (data_avail) ...

   SystemVerilog A6 (wrong):  in_response(1'b0, avail)
                              stall_r logic UNTOUCHED

   Same label. Different mutation. The SV version left the
   counter correct, so only the PID check could ever fire.

The SystemVerilog design had factored the halted test into a function and a separate counter statement, so mutating the function touched one of the two reads. Fixing it meant mutating the whole three-line region so that both reads are neutralised, exactly as the Verilog does. A6 became 79,414 in SystemVerilog too — identical to Verilog, as it should be, since Icarus gives both the same stimulus.

13. The Follow-Ups, and What They Are Testing

Answering section 2 well guarantees a follow-up. These are the five that actually come, in roughly the order they come:

"So how does a device signal that something happened?" It does not. It waits to be polled and then says so. Interrupt transfers are the host promising to ask at least every N frames. The name is the single most misleading word in the specification.

"What if the device isn't ready?" NAK — and NAK is not an error, it is flow control. The host retries in a later slot. The follow-up to the follow-up is "what if it NAKs forever?", and the answer is that nothing in the protocol stops it: the driver times out, which is why a device that NAKs permanently presents as a hang rather than as a failure.

"How does the host know a device is there?" It does not, until the hub reports a port status change — and the hub knows because of an electrical event, not a message. That is the only place in USB where information travels up the tree without being asked for, and it is deliberately outside the data protocol. Chapter 27.3 is this question.

"Can two devices talk at once?" No, and there is no mechanism to handle it if they do. The host's schedule makes it impossible; a hub that sees two talkers reports a collision rather than arbitrating, because arbitrating would destroy the evidence that the schedule was violated. Chapter 27.2 is this question.

"Why is isochronous unreliable?" Because a retry needs a slot, and the schedule already allocated every slot. Guaranteed bandwidth and guaranteed delivery are the same resource spent two different ways. Chapter 27.5 is this question.

14. UVM: Asserting the Property Instead of Describing It

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The defining property of USB is a NEGATIVE claim -- "the device never
// transmits unsolicited" -- and negative claims are where testbenches are
// weakest: an environment can run for a billion cycles without ever being
// in a position to observe one.
//
// So this monitor does two things that a data scoreboard does not:
//   1. it checks the negative claim on EVERY cycle, not on transactions;
//   2. it fails the run if the claim was never PUT AT RISK.
class tok_item extends uvm_sequence_item;
  `uvm_object_utils(tok_item)

  rand bit       valid;
  rand bit [1:0] pid;        // 0=OUT 1=IN 2=SOF 3=SETUP
  rand bit [6:0] addr;
  rand bit [1:0] ep;
  rand bit [3:0] data_avail;
  rand bit [3:0] halted;

  function new(string name = "tok_item"); super.new(name); endfunction

  // A bus on which every token is ours is not a bus, it is a point-to-point
  // link -- and it cannot distinguish this device from one with no address
  // comparator at all. Most traffic must belong to somebody else.
  constraint c_mostly_foreign { addr dist { 7'h2A := 25, [1:127] := 75 }; }
  constraint c_busy           { valid dist { 1 := 70, 0 := 30 }; }
endclass


class tok_gate_monitor extends uvm_component;
  `uvm_component_utils(tok_gate_monitor)

  virtual tg_if vif;

  // ---- the negative claim ----
  int unsigned n_unsolicited;

  // ---- and the evidence that it was at risk ----
  //
  // Each of these counts a situation in which a BROKEN design would have
  // transmitted. If they are all zero the run proved nothing, however many
  // cycles it lasted, and report_phase says so as an error.
  int unsigned n_wanted_no_token;   // data ready, no token at all
  int unsigned n_foreign_token;     // a token for another device
  int unsigned n_sof;               // a broadcast frame marker
  int unsigned n_superseded;        // a token replaced before its answer

  int unsigned n_resp, n_nak, n_stall;

  bit       p_acc;
  bit [1:0] p_ep, p_pid;

  function new(string name, uvm_component parent); super.new(name, parent);
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (!vif.rst_n) begin p_acc = 0; continue; end

      // ---- PROPERTY 1, every cycle: no permission, no transmission ----
      if (vif.tx_valid && !p_acc) begin
        n_unsolicited++;
        `uvm_error("USB/UNSOLICITED",
          $sformatf("device drove tx_pid=%0d with no token in the previous cycle",
                    vif.tx_pid))
      end

      // ---- PROPERTY 2: addressed means it MUST answer ----
      //
      // The positive half, and it is just as important. A device that
      // goes silent when polled is not safe, it is broken: the host
      // waits out the turnaround timeout, retries, and eventually
      // reports the endpoint as absent.
      if (p_acc && !vif.tx_valid)
        `uvm_error("USB/SILENT",
          "device was addressed and did not answer: silence is not a legal response")

      if (p_acc) begin
        n_resp++;
        if (vif.tx_pid == 3'd2) n_nak++;
        if (vif.tx_pid == 3'd3) n_stall++;
      end

      // ---- evidence gathering: was the claim ever at risk? ----
      if (!vif.tok_valid && |vif.data_avail)              n_wanted_no_token++;
      if (vif.tok_valid && vif.tok_addr != 7'h2A)         n_foreign_token++;
      if (vif.tok_valid && vif.tok_pid == 2'd2)           n_sof++;
      if (vif.tok_valid && p_acc)                         n_superseded++;

      p_acc = vif.tok_valid && (vif.tok_addr == 7'h2A) && (vif.tok_pid != 2'd2);
      if (p_acc) begin p_ep = vif.tok_ep; p_pid = vif.tok_pid; end
    end
  endtask

  function void report_phase(uvm_phase phase);
    super.report_phase(phase);

    `uvm_info("USB",
      $sformatf("%0d responses (%0d NAK, %0d STALL) | %0d unsolicited",
                n_resp, n_nak, n_stall, n_unsolicited), UVM_LOW)

    // A negative property needs its stimulus audited, not assumed. Each of
    // these is a run in which the design was never given the OPPORTUNITY to
    // break the rule -- and reporting PASS on such a run is the failure mode
    // this whole block exists to prevent.
    if (n_wanted_no_token == 0)
      `uvm_error("USB/COV",
        "the device never had data pending with no token on the bus: the central property was never at risk")
    if (n_foreign_token == 0)
      `uvm_error("USB/COV",
        "every token in this run was addressed to us: the address check was never exercised")
    if (n_sof == 0)
      `uvm_error("USB/COV",
        "no SOF was ever seen: the broadcast-token rule was never exercised")
    if (n_superseded == 0)
      `uvm_error("USB/COV",
        "no token was ever superseded before its answer was due")

    `uvm_info("USB/COV",
      $sformatf("at-risk cycles: %0d wanted-no-token, %0d foreign, %0d SOF, %0d superseded",
                n_wanted_no_token, n_foreign_token, n_sof, n_superseded), UVM_LOW)
  endfunction
endclass

15. Common Misconceptions

"USB devices can interrupt the host." No device on a USB bus can initiate anything. "Interrupt transfer" is a polling guarantee with an unfortunate name.

"NAK is an error." It is flow control, and a completely normal part of every bulk transfer. What is not normal is NAK forever — which the protocol permits and the driver has to time out.

"The device is idle, so the bus is idle." The bus is full of tokens for other devices. This design sits through four times more traffic than it answers.

"A full transmit buffer means the device will send." It means nothing at all until a token arrives. data_avail is high in every cycle of the first waveform and used in one.

"SOF is a token like any other." It is addressed to nobody. A device that answers it collides with every other device on the bus simultaneously.

"STALL means the transfer failed." It means the endpoint is halted and will stay halted until software clears it — which it does over the control endpoint, which is why SETUP can never be stalled.

"The host guarantees it will not supersede a token." It reorders and abandons constantly. Property 7 exists because the device must not answer a question the host has moved on from.

"A device with no address check only affects itself." It corrupts replies for every other device on the bus. A1 is the highest-scoring mutation here and the hardest to diagnose in the field.

"A low mutation score means a weak check." A5 scored 1093 and is a perfectly real defect. It scored 4 directed because the domain has four cells — and that was worth fixing.

16. Exercises

1. Give the ninety-second answer in your own words, then list five facts about USB and show each is a consequence of it. If any fact is not a consequence, your centre is in the wrong place.

2. A5's directed score equalled the number of scenarios in its domain. Derive that number from the sweep in section 10, and construct a second property of NAK that the loop phase still does not test.

3. Property 6 says SETUP is never stalled. Trace what happens to a device that stalls SETUP while halted, and show that it can never be recovered without a bus reset.

4. A1 makes a device answer for others. Design a bus-level monitor that identifies which device has the broken comparator, given only the traffic.

5. The bench counts n_unsolicited and asserts it is zero every cycle. Argue why asserting it once at the end would be insufficient, and give a design defect that only the per-cycle check catches.

6. Property 7 drops a superseded token. Argue the opposite — queue both and answer in order — and give the failure it causes on a real bus.

7. Extend the design to high speed, where a device may respond with NYET as well. Which of the seven properties change and which new one is needed?

17. Summary

IdeaWhy it matters
USB is host-scheduledone sentence from which everything else follows
A device may never initiatethere is no device-to-host interrupt line
Interrupt transfers are polledthe name is the worst in the specification
NAK means ask me againthe only way a responder expresses not-ready
NAK is a loop, not an answerone NAK and permanent NAK look identical
Frames exist because polling needs a budget1 ms full-speed, 125 µs microframes
Collisions are impossible, not arbitratedone initiator
SOF is answered by nobodya broadcast timestamp
STALL beats DATAa halted endpoint is halted with a full buffer
SETUP is never stalledit is how software clears a halt
A superseded token is never answeredthe host reorders and abandons
Audit the stimulus for a negative propertya run that never risked it proves nothing
An out-of-line column is usually the mutantA6 was 780× off in one language
A directed score equal to the scenario countis cornered, not killed
128 states, 7 mutations, 3 languages0 unsolicited transmissions in 331,561 checks

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o tg_v.out tg_v.v tg_v_tb.v && ./tg_v.out
SystemVerilogiverilog -g2012 -o tg_sv.out tg_sv.sv tg_sv_tb.sv && ./tg_sv.out
VHDL-2008 analysenvc --std=2008 -a tg_vhdl.vhd tg_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_tg_vhdl
VHDL-2008 runnvc --std=2008 -r tb_tg_vhdl
One mutationiverilog -g2005 -DMUT_A3 -o mm tg_v_mut.v tg_v_tb.v && ./mm
Directed only (Verilog)iverilog -g2005 -DDIRECTED_ONLY -o mm tg_v_mut.v tg_v_tb.v && ./mm
Directed only (VHDL)nvc --std=2008 -e -gDIRECTED_ONLY=true tb_tg_vhdl

All three implementations pass with 0 errors: all 128 combinations of token type, addressing, endpoint, data availability and halt state; 6305 responses issued against 27,781 tokens correctly ignored; zero unsolicited transmissions in 331,561 checks; and all seven mutations killed by directed stimulus alone.


Chapter 27.2 — Host / Device / Hub Identification is the follow-up you have just invited: if only the host may initiate, what exactly is a hub? Almost every candidate answers "a switch", and a hub is not a switch — it is a repeater with one upstream port, and the difference is a design in which no downstream port can reach another one at all.

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.