Skip to content
VLSI Mentor

USB · Module 27

The Enumeration Question

Attach to configured, with the one step almost everybody gets backwards — SET_ADDRESS takes effect after the status stage, and a device that switches early is invisible to the host.

Chapter 27.1 said a device may never initiate. Chapter 27.2 said a hub only reports when asked. Both leave the same question open, and it is the one interviewers use to separate people who have read the specification from people who have brought a board up.

1. The Question

"A device is plugged in. Walk me through what happens, end to end."

This is the most-asked intermediate USB question and it has a well-known answer, which is exactly why it is useful: everybody can produce a sequence, so the interviewer is not listening for the sequence. They are listening for one step — and for whether you know why it is the way it is.

2. The Sequence

Enumeration, with the address change in its correct place

A sequence diagram of USB enumeration. The hub detects an electrical attach and reports a port status change only when the host next polls it. The host resets the port, which puts the device in the default state answering to address zero. The host reads the device descriptor at address zero to learn the control endpoint's maximum packet size. It then sends SET_ADDRESS, which the device records but does not apply; the device acknowledges from address zero, and only after that acknowledgement completes does it begin answering at the new address. The host then reads the full descriptor set at the new address and finally sends SET_CONFIGURATION, after whose status stage the device is configured and its non-control endpoints become usable.Attach to configuredHubHostDeviceelectrical attachdetected1 · GET_PORT_STATUS(polled)2 · connect-changeset3 · port RESET →DEFAULT state,address 04 · GET_DESCRIPTORat address 0bMaxPacketSize0 —needed foreverything after5 · SET_ADDRESS(n) —recorded, NOTappliedACK, sent fromaddress 0NOW the addressbecomes n6 · GET_DESCRIPTORat address n7 ·SET_CONFIGURATION(1)ACK — endpoints arenow usable
The acknowledgement in step 5 is the last thing this device ever says as address 0. The address changes after it, not before.

Two things in that diagram are worth more than the rest.

Step 1 is polled. The hub detected the attach electrically and did nothing about it. It waits to be asked, because it is a device and devices do not initiate. The only information in USB that travels up the tree unbidden is an electrical condition, and even that is converted to a pollable status bit before anything in the data protocol can see it.

Step 4 happens at address 0 for a reason. The host does not yet know the control endpoint's maximum packet size, and it cannot read a descriptor properly without it. So the first read is deliberately small and deliberately at the default address, and its only purpose is to learn how to do the next one.

The address changes after the acknowledgement, not before

A SET_ADDRESS request is recorded while the device address stays at zero, and the address changes only on the cycle after the status stage acknowledgementoutstanding: address is 0outstanding: address is 0applied: address is 7applied: address is7SET_ADDRESS(7) recordedSET_ADDRESS(7) recordedstill address 0still address 0status stage ACKstatus stage ACKnow address 7now address 7clksetup_validreq00505050505050505wValue077777777status_ackpend_kind00ADDRADDRADDRADDRADDRADDRADDRdev_addr000000777addressedt0t1t2t3t4t5t6t7t8
dev_addr is still 0 in cycles 1 to 4, while the request is outstanding. It becomes 7 only after status_ack.

addressed stays low for the whole outstanding window too. That matters: a device that marks itself addressed when the request arrives will accept a SET_CONFIGURATION it has no business accepting, and section 13 shows what it costs to catch.

3. Why This Rule Exists

It looks like pedantry and it is not. Consider what the host has to do if the device switched early.

The host sends SET_ADDRESS(7) to address 0 and then needs to know whether the device got it. There is exactly one mechanism for that: the status stage, which the host performs at the address it sent the request to. If the device has already moved to 7, the status stage goes unanswered — and the host cannot tell "the device moved early" from "the device is not there."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   Device switches EARLY:

     host -> addr 0 : SET_ADDRESS(7)
     host -> addr 0 : status stage       device is at 7; silence
     host            : timeout
     host -> addr 0 : SET_ADDRESS(7)     retry; device is at 7; silence
     host            : "no device here"

   Device switches AFTER the status stage:

     host -> addr 0 : SET_ADDRESS(7)
     host -> addr 0 : status stage       ACK from address 0
     device          : now I am 7
     host -> addr 7 : GET_DESCRIPTOR     works

4. The Second Lesson, Which Is Structural

There is a bug class in this design that has nothing to do with USB, and it cost more debugging time than the address rule did.

A first version kept the device's state and the outstanding request in one register, which is the obvious way to write an FSM. It is wrong here, because the two are not the same thing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   One register, `st`:

     SETUP(SET_CONFIG) arrives   -> st <= S_CONFIG_PEND
     status stage for SET_ADDRESS-> st <= S_ADDRESSED

   Both in the same cycle. Non-blocking, so the LATER write wins
   and the other request is SILENTLY DROPPED.

   Two registers:

     pend_kind  -- which request is outstanding
     have_addr / conf_r -- what has actually COMPLETED

   The status stage runs FIRST and retires pend_kind; the SETUP
   decode runs SECOND and sets it. The new request survives, and
   the completed one is recorded where nothing can overwrite it.

So in the published design state is not a register at all — it is a decode of pend_kind, have_addr and conf_r. Nothing in the design branches on it, which means it cannot drift out of step with what the device actually does.

5. Seven Properties

#Property
1The live address is the one the last completed SET_ADDRESS status stage established.
2While a SET_ADDRESS is outstanding, the address has not moved.
3addressed is false until a SET_ADDRESS status stage completes.
4A bus reset returns the device to address 0, unaddressed and unconfigured, from any state.
5SET_CONFIGURATION is refused unless a SET_ADDRESS has completed — gated on that, not on the address being non-zero.
6A completed re-address unconfigures the device.
7A status stage with nothing outstanding changes nothing.

Property 5's parenthesis is the kind of detail that separates a working device from a nearly-working one: SET_ADDRESS(0) is legal and means "return to the default address". Gating configuration on addr != 0 therefore refuses to configure a device the host has deliberately returned to address 0, and — worse — the obvious self-check written that way produces thousands of false alarms. Section 13 has that story.

6. Verilog-2005 RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_enum_fsm -- "Walk me through enumeration" answered in hardware.
//
//  The sequence is easy to recite and has one step almost everybody gets
//  backwards:
//
//      SET_ADDRESS takes effect AFTER the status stage completes,
//      not when the request arrives.
//
//  The device must acknowledge the request at its OLD address -- which for
//  a freshly attached device is address 0 -- and only then start answering
//  at the new one. A device that switches early never sends the
//  acknowledgement the host is waiting for: the host times out, retries at
//  address 0, gets nothing (the device has moved), and gives up. The device
//  works perfectly and is never seen.
//
//  The second lesson is structural. The device STATE and the OUTSTANDING
//  REQUEST are two different things, and a design that keeps them in one
//  register silently drops a request whenever a new SETUP lands in the same
//  cycle as a status stage for a different one. They are separate here.
// =====================================================================
module usb_enum_fsm (
  input  wire        clk,
  input  wire        rst_n,

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

  // ---- a control transfer, decomposed into its three stages ----
  input  wire        setup_valid,  // an 8-byte SETUP packet arrived
  input  wire [7:0]  req,          // bRequest
  input  wire [15:0] val,          // wValue
  input  wire        data_stage,   // a DATA stage packet moved
  input  wire        status_ack,   // the STATUS stage completed (ACK seen)

  // ---- bus events outside the data protocol ----
  input  wire        bus_reset,

  // ---- what the device answers to, and where it is in its life ----
  output wire [6:0]  dev_addr,
  output wire [2:0]  state,
  output wire        configured,
  output wire        addressed,

  // ---- observability ----
  output wire [31:0] n_setup,
  output wire [31:0] n_addr_change,
  output wire [31:0] n_early_switch,      // must always read 0
  output wire [31:0] n_reset_to_default,
  output wire [31:0] n_wrong_addr,
  output wire [31:0] n_dropped_request
);

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

  localparam [7:0] R_GET_DESCRIPTOR = 8'h06,
                   R_SET_ADDRESS    = 8'h05,
                   R_SET_CONFIG     = 8'h09;

  // ---- reported device state, from the specification ----
  //
  // ATTACHED and POWERED are electrical. A device's LOGIC begins at
  // DEFAULT, which is the state in which it answers to address 0.
  localparam [2:0] S_DEFAULT     = 3'd0,
                   S_ADDR_PEND   = 3'd1,   // SET_ADDRESS seen, NOT applied
                   S_ADDRESSED   = 3'd2,
                   S_CONFIG_PEND = 3'd3,
                   S_CONFIGURED  = 3'd4;

  // ---- the OUTSTANDING REQUEST, which is not the state ----
  localparam [1:0] P_NONE = 2'd0, P_ADDR = 2'd1, P_CONF = 2'd2;

  reg [6:0] addr_r;
  reg [6:0] pend_addr;
  reg [1:0] pend_kind;
  // The live address as it stood when the request was recorded. The
  // self-check below needs to know whether the address MOVED while a
  // request was outstanding -- not whether it happens to equal the
  // requested value, which it legitimately does for SET_ADDRESS(0).
  reg [6:0] addr_at_req;
  reg       have_addr;   // a SET_ADDRESS status stage has completed
  reg       conf_r;

  reg [31:0] setup_c, chg_c, early_c, rst_c, wrong_c, drop_c;

  // `state` is a DECODE of the real registers, not a register of its own.
  // Nothing in the design branches on it, so it cannot go out of step with
  // what the device actually does.
  assign state = bus_reset          ? S_DEFAULT
               : (pend_kind == P_ADDR) ? S_ADDR_PEND
               : (pend_kind == P_CONF) ? S_CONFIG_PEND
               : conf_r             ? S_CONFIGURED
               : have_addr          ? S_ADDRESSED
                                    : S_DEFAULT;

  assign dev_addr           = addr_r;
  assign configured         = conf_r;
  assign addressed          = have_addr;
  assign n_setup            = setup_c;
  assign n_addr_change      = chg_c;
  assign n_early_switch     = early_c;
  assign n_reset_to_default = rst_c;
  assign n_wrong_addr       = wrong_c;
  assign n_dropped_request  = drop_c;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      addr_r      <= 7'd0;
      pend_addr   <= 7'd0;
      pend_kind   <= P_NONE;
      addr_at_req <= 7'd0;
      have_addr   <= 1'b0;
      conf_r      <= 1'b0;
      setup_c     <= 32'd0;
      chg_c     <= 32'd0;
      early_c   <= 32'd0;
      rst_c     <= 32'd0;
      wrong_c   <= 32'd0;
      drop_c    <= 32'd0;
    end else if (bus_reset) begin
      // ---- a bus reset returns the device to DEFAULT ----
      //
      // Address 0, unaddressed, unconfigured, from any state, every time.
      // This is why enumeration can always be restarted, and why a wedged
      // device recovers on replug: the host resets the port first.
      addr_r      <= 7'd0;
      pend_addr   <= 7'd0;
      pend_kind   <= P_NONE;
      addr_at_req <= 7'd0;
      have_addr   <= 1'b0;
      conf_r      <= 1'b0;
      rst_c       <= rst_c + 32'd1;
    end else begin
      // A token addressed to somebody else is not ours to act on.
      if (tok_valid && (tok_addr != addr_r) && (tok_pid != T_SOF))
        wrong_c <= wrong_c + 32'd1;

      // =============================================================
      //  STATUS STAGE FIRST.
      //
      //  It resolves whatever was outstanding BEFORE this cycle. Running
      //  it before the SETUP decode below means a new request arriving on
      //  the same edge overwrites `pend_kind` afterwards and is therefore
      //  kept -- rather than being cancelled by this block's write.
      // =============================================================
      if (status_ack) begin
        pend_kind <= P_NONE;

        if (pend_kind == P_ADDR) begin
          // NOW. Not earlier. The acknowledgement has already been sent
          // from the old address and the host has seen it.
          addr_r    <= pend_addr;
          have_addr <= 1'b1;
          // Re-addressing returns the device to the Address state, which
          // is by definition unconfigured.
          conf_r    <= 1'b0;
          chg_c     <= chg_c + 32'd1;
        end else if (pend_kind == P_CONF) begin
          conf_r <= 1'b1;
        end
      end

      // =============================================================
      //  SETUP DECODE SECOND, so the newest request wins.
      // =============================================================
      if (setup_valid) begin
        setup_c <= setup_c + 32'd1;

        // A SETUP that displaces a still-outstanding request is counted.
        // It is legal -- the host may abandon a transfer at any time --
        // but a design that does it by accident looks identical, so the
        // number is published rather than assumed to be zero.
        if ((pend_kind != P_NONE) && !status_ack)
          drop_c <= drop_c + 32'd1;

        case (req)
          R_SET_ADDRESS: begin
            // Recorded. NOT applied.
            pend_addr   <= val[6:0];
            pend_kind   <= P_ADDR;
            // The address that will be LIVE after this edge -- which is not
            // addr_r when a status stage is completing a previous
            // SET_ADDRESS on this same edge.
            addr_at_req <= (status_ack && (pend_kind == P_ADDR)) ? pend_addr
                                                                 : addr_r;
          end

          R_SET_CONFIG: begin
            // Only meaningful once a SET_ADDRESS has actually COMPLETED.
            // Gated on have_addr rather than on the address being
            // non-zero, because SET_ADDRESS(0) is legal and means
            // "return to the default address".
            if (have_addr) pend_kind <= P_CONF;
            else           pend_kind <= P_NONE;
          end

          R_GET_DESCRIPTOR: begin
            // Legal in every state, including DEFAULT at address 0 -- it
            // is how the host learns the packet size it must use for
            // everything that follows. It leaves no state pending.
            pend_kind <= P_NONE;
          end

          default: pend_kind <= P_NONE;
        endcase
      end

      // ---- the self-check that makes the central rule measurable ----
      //
      // While a SET_ADDRESS is outstanding, the live address must not have
      // MOVED from what it was when the request arrived. On a correct design
      // this is unreachable; it is counted rather than asserted so that a run
      // can publish the number zero.
      //
      // The first version of this check compared addr_r against pend_addr,
      // which fires on the perfectly legal SET_ADDRESS(0) to a device that is
      // already at address 0 -- 9607 false positives in one run. A checker
      // that cries wolf is a checker somebody switches off.
      if ((pend_kind == P_ADDR) && (addr_r != addr_at_req))
        early_c <= early_c + 32'd1;
    end
  end

endmodule

7. SystemVerilog RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_enum_fsm -- SystemVerilog.
//
//  The requests and the outstanding-request kind are named enumerations
//  rather than magic numbers, which matters more here than usual: this
//  design's central bug class is confusing WHICH request is outstanding
//  with WHERE the device is in its life, and two enums with different
//  types cannot be mixed up by accident.
//
//  The sequence is easy to recite and has one step almost everybody gets
//  backwards:
//
//      SET_ADDRESS takes effect AFTER the status stage completes,
//      not when the request arrives.
//
//  The device must acknowledge the request at its OLD address -- which for
//  a freshly attached device is address 0 -- and only then start answering
//  at the new one. A device that switches early never sends the
//  acknowledgement the host is waiting for: the host times out, retries at
//  address 0, gets nothing (the device has moved), and gives up. The device
//  works perfectly and is never seen.
//
//  The second lesson is structural. The device STATE and the OUTSTANDING
//  REQUEST are two different things, and a design that keeps them in one
//  register silently drops a request whenever a new SETUP lands in the same
//  cycle as a status stage for a different one. They are separate here.
// =====================================================================
module usb_enum_fsm (
  input  logic       clk,
  input  logic       rst_n,

  // ---- the bus ----
  input  logic       tok_valid,
  input  logic [1:0] tok_pid,      // T_OUT / T_IN / T_SOF / T_SETUP
  input  logic [6:0] tok_addr,

  // ---- a control transfer, decomposed into its three stages ----
  input  logic       setup_valid,  // an 8-byte SETUP packet arrived
  input  logic [7:0] req,          // bRequest
  input  logic [15:0]val,          // wValue
  input  logic       data_stage,   // a DATA stage packet moved
  input  logic       status_ack,   // the STATUS stage completed (ACK seen)

  // ---- bus events outside the data protocol ----
  input  logic       bus_reset,

  // ---- what the device answers to, and where it is in its life ----
  output logic [6:0] dev_addr,
  output logic [2:0] state,
  output logic       configured,
  output logic       addressed,

  // ---- observability ----
  output logic [31:0]n_setup,
  output logic [31:0]n_addr_change,
  output logic [31:0]n_early_switch,      // must always read 0
  output logic [31:0]n_reset_to_default,
  output logic [31:0]n_wrong_addr,
  output logic [31:0]n_dropped_request
);

  typedef enum logic [1:0] { T_OUT = 2'd0, T_IN = 2'd1,
                             T_SOF = 2'd2, T_SETUP = 2'd3 } tok_e;

  localparam [7:0] R_GET_DESCRIPTOR = 8'h06,
                   R_SET_ADDRESS    = 8'h05,
                   R_SET_CONFIG     = 8'h09;

  // ---- reported device state, from the specification ----
  //
  // ATTACHED and POWERED are electrical. A device's LOGIC begins at
  // DEFAULT, which is the state in which it answers to address 0.
  localparam [2:0] S_DEFAULT     = 3'd0,
                   S_ADDR_PEND   = 3'd1,   // SET_ADDRESS seen, NOT applied
                   S_ADDRESSED   = 3'd2,
                   S_CONFIG_PEND = 3'd3,
                   S_CONFIGURED  = 3'd4;

  // ---- the OUTSTANDING REQUEST, which is not the state ----
  // A distinct TYPE from tok_e, so the compiler refuses to confuse the
  // outstanding request with a token PID.
  typedef enum logic [1:0] { P_NONE = 2'd0, P_ADDR = 2'd1,
                             P_CONF = 2'd2 } pend_e;

  logic [6:0] addr_r;
  logic [6:0] pend_addr;
  pend_e      pend_kind;
  // The live address as it stood when the request was recorded. The
  // self-check below needs to know whether the address MOVED while a
  // request was outstanding -- not whether it happens to equal the
  // requested value, which it legitimately does for SET_ADDRESS(0).
  logic [6:0] addr_at_req;
  logic     have_addr;   // a SET_ADDRESS status stage has completed
  logic     conf_r;

  logic [31:0] setup_c, chg_c, early_c, rst_c, wrong_c, drop_c;

  // `state` is a DECODE of the real registers, not a register of its own.
  // Nothing in the design branches on it, so it cannot go out of step with
  // what the device actually does.
  assign state = bus_reset          ? S_DEFAULT
               : (pend_kind == P_ADDR) ? S_ADDR_PEND
               : (pend_kind == P_CONF) ? S_CONFIG_PEND
               : conf_r             ? S_CONFIGURED
               : have_addr          ? S_ADDRESSED
                                    : S_DEFAULT;

  assign dev_addr           = addr_r;
  assign configured         = conf_r;
  assign addressed          = have_addr;
  assign n_setup            = setup_c;
  assign n_addr_change      = chg_c;
  assign n_early_switch     = early_c;
  assign n_reset_to_default = rst_c;
  assign n_wrong_addr       = wrong_c;
  assign n_dropped_request  = drop_c;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      addr_r      <= 7'd0;
      pend_addr   <= 7'd0;
      pend_kind   <= P_NONE;
      addr_at_req <= 7'd0;
      have_addr   <= 1'b0;
      conf_r      <= 1'b0;
      setup_c     <= '0;
      chg_c     <= 32'd0;
      early_c   <= 32'd0;
      rst_c     <= 32'd0;
      wrong_c   <= 32'd0;
      drop_c    <= 32'd0;
    end else if (bus_reset) begin
      // ---- a bus reset returns the device to DEFAULT ----
      //
      // Address 0, unaddressed, unconfigured, from any state, every time.
      // This is why enumeration can always be restarted, and why a wedged
      // device recovers on replug: the host resets the port first.
      addr_r      <= 7'd0;
      pend_addr   <= 7'd0;
      pend_kind   <= P_NONE;
      addr_at_req <= 7'd0;
      have_addr   <= 1'b0;
      conf_r      <= 1'b0;
      rst_c       <= rst_c + 32'd1;
    end else begin
      // A token addressed to somebody else is not ours to act on.
      if (tok_valid && (tok_addr != addr_r) && (tok_pid != T_SOF))
        wrong_c <= wrong_c + 32'd1;

      // =============================================================
      //  STATUS STAGE FIRST.
      //
      //  It resolves whatever was outstanding BEFORE this cycle. Running
      //  it before the SETUP decode below means a new request arriving on
      //  the same edge overwrites `pend_kind` afterwards and is therefore
      //  kept -- rather than being cancelled by this block's write.
      // =============================================================
      if (status_ack) begin
        pend_kind <= P_NONE;

        if (pend_kind == P_ADDR) begin
          // NOW. Not earlier. The acknowledgement has already been sent
          // from the old address and the host has seen it.
          addr_r    <= pend_addr;
          have_addr <= 1'b1;
          // Re-addressing returns the device to the Address state, which
          // is by definition unconfigured.
          conf_r    <= 1'b0;
          chg_c     <= chg_c + 32'd1;
        end else if (pend_kind == P_CONF) begin
          conf_r <= 1'b1;
        end
      end

      // =============================================================
      //  SETUP DECODE SECOND, so the newest request wins.
      // =============================================================
      if (setup_valid) begin
        setup_c <= setup_c + 32'd1;

        // A SETUP that displaces a still-outstanding request is counted.
        // It is legal -- the host may abandon a transfer at any time --
        // but a design that does it by accident looks identical, so the
        // number is published rather than assumed to be zero.
        if ((pend_kind != P_NONE) && !status_ack)
          drop_c <= drop_c + 32'd1;

        case (req)
          R_SET_ADDRESS: begin
            // Recorded. NOT applied.
            pend_addr   <= val[6:0];
            pend_kind   <= P_ADDR;
            // The address that will be LIVE after this edge -- which is not
            // addr_r when a status stage is completing a previous
            // SET_ADDRESS on this same edge.
            addr_at_req <= (status_ack && (pend_kind == P_ADDR)) ? pend_addr
                                                                 : addr_r;
          end

          R_SET_CONFIG: begin
            // Only meaningful once a SET_ADDRESS has actually COMPLETED.
            // Gated on have_addr rather than on the address being
            // non-zero, because SET_ADDRESS(0) is legal and means
            // "return to the default address".
            if (have_addr) pend_kind <= P_CONF;
            else           pend_kind <= P_NONE;
          end

          R_GET_DESCRIPTOR: begin
            // Legal in every state, including DEFAULT at address 0 -- it
            // is how the host learns the packet size it must use for
            // everything that follows. It leaves no state pending.
            pend_kind <= P_NONE;
          end

          default: pend_kind <= P_NONE;
        endcase
      end

      // ---- the self-check that makes the central rule measurable ----
      //
      // While a SET_ADDRESS is outstanding, the live address must not have
      // MOVED from what it was when the request arrived. On a correct design
      // this is unreachable; it is counted rather than asserted so that a run
      // can publish the number zero.
      //
      // The first version of this check compared addr_r against pend_addr,
      // which fires on the perfectly legal SET_ADDRESS(0) to a device that is
      // already at address 0 -- 9607 false positives in one run. A checker
      // that cries wolf is a checker somebody switches off.
      if ((pend_kind == P_ADDR) && (addr_r != addr_at_req))
        early_c <= early_c + 32'd1;
    end
  end

endmodule

8. VHDL-2008 RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  usb_enum_fsm -- VHDL-2008.
--
--  VHDL gives the two things this design must not confuse -- the device
--  STATE and the OUTSTANDING REQUEST -- genuinely distinct enumeration
--  types, so mixing them is a compile error rather than a silent bug.
--
--  Note the constant names: VHDL identifiers are case-INSENSITIVE, so a
--  package constant `T_IN` and a port `t_in` would be the same name and
--  the port would win silently. Everything here is prefixed.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package en_pkg is
  type dev_state_t is (ST_DEFAULT, ST_ADDR_PEND, ST_ADDRESSED,
                       ST_CONFIG_PEND, ST_CONFIGURED);

  -- A separate type from dev_state_t. The whole point.
  type pend_t is (PK_NONE, PK_ADDR, PK_CONF);

  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 REQ_GET_DESCRIPTOR : std_logic_vector(7 downto 0) := x"06";
  constant REQ_SET_ADDRESS    : std_logic_vector(7 downto 0) := x"05";
  constant REQ_SET_CONFIG     : std_logic_vector(7 downto 0) := x"09";

  function state_code(s : dev_state_t) return std_logic_vector;
end package;

package body en_pkg is
  function state_code(s : dev_state_t) return std_logic_vector is
  begin
    case s is
      when ST_DEFAULT     => return "000";
      when ST_ADDR_PEND   => return "001";
      when ST_ADDRESSED   => return "010";
      when ST_CONFIG_PEND => return "011";
      when ST_CONFIGURED  => return "100";
    end case;
  end function;
end package body;

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

entity usb_enum_fsm is
  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);

    setup_valid : in  std_logic;
    req         : in  std_logic_vector(7 downto 0);
    val         : in  std_logic_vector(15 downto 0);
    data_stage  : in  std_logic;
    status_ack  : in  std_logic;

    bus_reset   : in  std_logic;

    dev_addr    : out std_logic_vector(6 downto 0);
    state       : out std_logic_vector(2 downto 0);
    configured  : out std_logic;
    addressed   : out std_logic;

    n_setup            : out std_logic_vector(31 downto 0);
    n_addr_change      : out std_logic_vector(31 downto 0);
    n_early_switch     : out std_logic_vector(31 downto 0);
    n_reset_to_default : out std_logic_vector(31 downto 0);
    n_wrong_addr       : out std_logic_vector(31 downto 0);
    n_dropped_request  : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of usb_enum_fsm is
  signal addr_r      : std_logic_vector(6 downto 0) := (others => '0');
  signal pend_addr   : std_logic_vector(6 downto 0) := (others => '0');
  signal addr_at_req : std_logic_vector(6 downto 0) := (others => '0');
  signal pend_kind   : pend_t := PK_NONE;
  signal have_addr   : std_logic := '0';
  signal conf_r      : std_logic := '0';

  signal setup_c, chg_c, early_c, rst_c, wrong_c, drop_c
    : unsigned(31 downto 0) := (others => '0');
begin

  dev_addr   <= addr_r;
  configured <= conf_r;
  addressed  <= have_addr;

  -- `state` is a DECODE of the real registers, never a register of its own.
  -- Nothing in the design branches on it, so it cannot drift out of step
  -- with what the device actually does.
  state <= state_code(ST_DEFAULT)     when bus_reset = '1'     else
           state_code(ST_ADDR_PEND)   when pend_kind = PK_ADDR else
           state_code(ST_CONFIG_PEND) when pend_kind = PK_CONF else
           state_code(ST_CONFIGURED)  when conf_r = '1'        else
           state_code(ST_ADDRESSED)   when have_addr = '1'     else
           state_code(ST_DEFAULT);

  n_setup            <= std_logic_vector(setup_c);
  n_addr_change      <= std_logic_vector(chg_c);
  n_early_switch     <= std_logic_vector(early_c);
  n_reset_to_default <= std_logic_vector(rst_c);
  n_wrong_addr       <= std_logic_vector(wrong_c);
  n_dropped_request  <= std_logic_vector(drop_c);

  process(clk, rst_n)
  begin
    if rst_n = '0' then
      addr_r      <= (others => '0');
      pend_addr   <= (others => '0');
      addr_at_req <= (others => '0');
      pend_kind   <= PK_NONE;
      have_addr   <= '0';
      conf_r      <= '0';
      setup_c     <= (others => '0');
      chg_c       <= (others => '0');
      early_c     <= (others => '0');
      rst_c       <= (others => '0');
      wrong_c     <= (others => '0');
      drop_c      <= (others => '0');

    elsif rising_edge(clk) then
      if bus_reset = '1' then
        -- A bus reset returns the device to DEFAULT: address 0,
        -- unaddressed, unconfigured, from any state, every time.
        addr_r      <= (others => '0');
        pend_addr   <= (others => '0');
        addr_at_req <= (others => '0');
        pend_kind   <= PK_NONE;
        have_addr   <= '0';
        conf_r      <= '0';
        rst_c       <= rst_c + 1;
      else
        if tok_valid = '1' and tok_addr /= addr_r and tok_pid /= TOK_SOF then
          wrong_c <= wrong_c + 1;
        end if;

        -- ===========================================================
        --  STATUS STAGE FIRST, so a new request arriving on this same
        --  edge overwrites pend_kind afterwards and is therefore kept.
        -- ===========================================================
        if status_ack = '1' then
          pend_kind <= PK_NONE;

          if pend_kind = PK_ADDR then
            -- NOW. The acknowledgement has already gone out from the
            -- old address and the host has seen it.
            addr_r    <= pend_addr;
            have_addr <= '1';
            conf_r    <= '0';        -- re-addressing unconfigures
            chg_c     <= chg_c + 1;
          elsif pend_kind = PK_CONF then
            conf_r <= '1';
          end if;
        end if;

        -- ===========================================================
        --  SETUP DECODE SECOND, so the newest request wins.
        -- ===========================================================
        if setup_valid = '1' then
          setup_c <= setup_c + 1;

          if pend_kind /= PK_NONE and status_ack = '0' then
            drop_c <= drop_c + 1;
          end if;

          if req = REQ_SET_ADDRESS then
            -- Recorded. NOT applied.
            pend_addr <= val(6 downto 0);
            pend_kind <= PK_ADDR;
            -- The address that will be LIVE after this edge, which is not
            -- addr_r when a status stage is completing a previous
            -- SET_ADDRESS on this same edge.
            if status_ack = '1' and pend_kind = PK_ADDR then
              addr_at_req <= pend_addr;
            else
              addr_at_req <= addr_r;
            end if;

          elsif req = REQ_SET_CONFIG then
            -- Only once a SET_ADDRESS has actually COMPLETED. Gated on
            -- have_addr rather than on the address being non-zero,
            -- because SET_ADDRESS(0) is legal and means "return to the
            -- default address".
            if have_addr = '1' then pend_kind <= PK_CONF;
            else                    pend_kind <= PK_NONE;
            end if;

          elsif req = REQ_GET_DESCRIPTOR then
            -- Legal in every state, including DEFAULT at address 0.
            pend_kind <= PK_NONE;
          else
            pend_kind <= PK_NONE;
          end if;
        end if;

        -- ---- the self-check that makes the central rule measurable ----
        --
        -- While a SET_ADDRESS is outstanding the live address must not have
        -- MOVED from what it was when the request arrived. Unreachable on a
        -- correct design, and counted rather than asserted so a run can
        -- publish the number zero.
        if pend_kind = PK_ADDR and addr_r /= addr_at_req then
          early_c <= early_c + 1;
        end if;
      end if;
    end if;
  end process;

end architecture;

9. How the Testbench Knows the Answer

The shadow model does not mirror the FSM. It answers a different question: "what is the last address a completed SET_ADDRESS status stage established, since the most recent bus reset?" — a claim about completed transfers rather than about states.

That independence had to be fought for, and the fight is the most useful thing in this chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   First version of the shadow (WRONG):

     if (setup_valid && req == SET_ADDRESS)
        s_pend_valid = 1;          // blocking
     if (status_ack && s_pend_valid)
        s_addr = s_pend;           // sees the line above!

   The model applied the address in the SAME CYCLE the request
   arrived -- which is precisely the defect under test. It was
   asserting the bug and calling the correct design wrong:
   145,208 errors, 7248 "premature switches", design fine.

The fix is to snapshot the outstanding-request state as it stood before the cycle, and resolve the status stage against that snapshot — which is exactly what the hardware does, because the hardware reads registered values.

have_addr needed the same treatment for the same reason: the device gates SET_CONFIGURATION on its registered have_addr, so a SET_ADDRESS completing on the same edge does not yet authorise a SET_CONFIGURATION arriving with it. A shadow that had already updated its own copy disagreed 28,386 times.

Verilog-2005 testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for usb_enum_fsm.
//
//  The shadow model does not mirror the FSM. It answers a different
//  question: "what is the last address a COMPLETED SET_ADDRESS status
//  stage established, since the most recent bus reset?" -- a claim about
//  completed transfers rather than about states, so a defect in the state
//  machine cannot hide inside an identical state machine.
// =====================================================================
`timescale 1ns/1ps
module tb_en_v;

  localparam [1:0] T_OUT = 2'd0, T_IN = 2'd1, T_SOF = 2'd2, T_SETUP = 2'd3;
  localparam [7:0] R_GET_DESCRIPTOR = 8'h06, R_SET_ADDRESS = 8'h05,
                   R_SET_CONFIG     = 8'h09, R_OTHER       = 8'h0B;
  localparam [2:0] S_DEFAULT = 3'd0, S_ADDR_PEND = 3'd1, S_ADDRESSED = 3'd2,
                   S_CONFIG_PEND = 3'd3, S_CONFIGURED = 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         setup_valid = 1'b0;
  reg  [7:0]  req = 8'd0;
  reg  [15:0] val = 16'd0;
  reg         data_stage = 1'b0;
  reg         status_ack = 1'b0;
  reg         bus_reset  = 1'b0;

  wire [6:0]  dev_addr;
  wire [2:0]  state;
  wire        configured, addressed;
  wire [31:0] n_setup, n_addr_change, n_early_switch,
              n_reset_to_default, n_wrong_addr, n_dropped_request;

  usb_enum_fsm dut (
    .clk(clk), .rst_n(rst_n),
    .tok_valid(tok_valid), .tok_pid(tok_pid), .tok_addr(tok_addr),
    .setup_valid(setup_valid), .req(req), .val(val),
    .data_stage(data_stage), .status_ack(status_ack),
    .bus_reset(bus_reset),
    .dev_addr(dev_addr), .state(state),
    .configured(configured), .addressed(addressed),
    .n_setup(n_setup), .n_addr_change(n_addr_change),
    .n_early_switch(n_early_switch),
    .n_reset_to_default(n_reset_to_default), .n_wrong_addr(n_wrong_addr),
    .n_dropped_request(n_dropped_request)
  );

  always #5 clk = ~clk;

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

  integer seed;

  // ---- $random is SIGNED: mask the sign bit before any modulo ----
  function [31:0] urand;
    input dummy;
    begin urand = $random(seed) & 32'h3FFF_FFFF; end
  endfunction

  // ---- the shadow: a claim about completed transfers ----
  reg [6:0]  s_addr;        // address established by the last completed SET_ADDRESS
  reg [6:0]  s_pend;        // address a SETUP has requested but not yet established
  reg        s_pend_valid;  // a SET_ADDRESS is outstanding
  reg        s_conf;        // configured
  reg        s_conf_pend;
  reg        s_have_addr;  // a SET_ADDRESS status stage has completed
  // Snapshots of the outstanding-request state as it stood BEFORE this
  // cycle. The design decides the status stage from its REGISTERED state,
  // so a shadow using blocking assignments would apply a SET_ADDRESS in
  // the same cycle its SETUP arrived -- which is precisely the defect
  // under test, asserted by the model instead of checked.
  reg        p_pend_valid, p_conf_pend, p_have_addr;
  reg [6:0]  p_pend;
  reg [31:0] x_setup, x_chg, x_rst, x_wrong, x_drop;

  // ---- the headline counter ----
  //
  // Every cycle in which the live address differed from the address the
  // last COMPLETED status stage established. On a correct device this is
  // structurally impossible, and the run publishes the number.
  integer n_premature = 0;

  // ---- exhaustive reach over (state, request, ack, reset) ----
  reg reach [0:79];
  integer ri, n_reach;
  // ---- and over every address SET_ADDRESS can name ----
  reg areach [0:127];
  integer ai2, n_areach;

  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.
  // ---------------------------------------------------------------
  task step(input sv, input [7:0] rq, input [15:0] vl,
            input ds, input sa, input br,
            input tv, input [1:0] tp, input [6:0] ta);
    reg [6:0] e_addr;
    reg       e_conf;
    begin
      setup_valid = sv;  req = rq;  val = vl;
      data_stage  = ds;  status_ack = sa;  bus_reset = br;
      tok_valid   = tv;  tok_pid = tp;  tok_addr = ta;

      // ---- advance the shadow to what SHOULD hold after this edge ----
      if (br) begin
        s_addr = 7'd0;  s_pend = 7'd0;  s_pend_valid = 1'b0;
        s_conf = 1'b0;  s_conf_pend = 1'b0;  s_have_addr = 1'b0;
        x_rst  = x_rst + 1;
      end else begin
        if (tv && (tp != T_SOF) && (ta != s_addr)) x_wrong = x_wrong + 1;

        // Snapshot of what was outstanding BEFORE this cycle. The status
        // stage resolves THAT, never a request arriving on the same edge.
        p_pend_valid = s_pend_valid;
        p_pend       = s_pend;
        p_conf_pend  = s_conf_pend;
        // have_addr is snapshotted too: the device gates SET_CONFIG on its
        // REGISTERED value, so a SET_ADDRESS completing on this same edge
        // does not yet authorise a SET_CONFIG arriving with it.
        p_have_addr  = s_have_addr;

        // ---- status stage first, exactly as the device orders it ----
        if (sa) begin
          s_pend_valid = 1'b0;
          s_conf_pend  = 1'b0;
          if (p_pend_valid) begin
            s_addr = p_pend;  s_have_addr = 1'b1;
            s_conf = 1'b0;                     // re-addressing unconfigures
            x_chg  = x_chg + 1;
          end else if (p_conf_pend) begin
            s_conf = 1'b1;
          end
        end

        // ---- then the new request, which displaces whatever was there ----
        if (sv) begin
          x_setup = x_setup + 1;
          if (p_pend_valid || p_conf_pend) begin
            if (!sa) x_drop = x_drop + 1;
          end
          if (rq == R_SET_ADDRESS) begin
            s_pend = vl[6:0];  s_pend_valid = 1'b1;  s_conf_pend = 1'b0;
          end else if (rq == R_SET_CONFIG) begin
            s_pend_valid = 1'b0;
            s_conf_pend  = p_have_addr;
          end else begin
            s_pend_valid = 1'b0;  s_conf_pend = 1'b0;
          end
        end
      end

      e_addr = s_addr;
      e_conf = s_conf;

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

      // ---- PROPERTY 1: the live address is the last COMPLETED one ----
      if (dev_addr !== e_addr) n_premature = n_premature + 1;
      ck(dev_addr === e_addr, "live address is not the last completed address");

      // ---- PROPERTY 2: configured only after its own status stage ----
      ck(configured === e_conf, "configured flag disagrees");

      // ---- PROPERTY 3: a pending SET_ADDRESS has NOT taken effect ----
      //
      // The rule stated directly: while a SET_ADDRESS is outstanding the
      // device must still be answering at the address it had before.
      if (s_pend_valid && (s_pend != s_addr))
        ck(dev_addr !== s_pend,
           "device switched to the new address before the status stage");

      // ---- PROPERTY 4: counters agree with an independent tally ----
      ck(n_setup            === x_setup, "SETUP count disagrees");
      ck(n_addr_change      === x_chg,   "address-change count disagrees");
      ck(n_reset_to_default === x_rst,   "reset count disagrees");
      ck(n_wrong_addr       === x_wrong, "wrong-address count disagrees");
      ck(n_dropped_request  === x_drop,  "dropped-request count disagrees");

      // ---- PROPERTY 5: the design's own early-switch counter is 0 ----
      ck(n_early_switch === 32'd0, "the design detected its own early switch");
      ck(n_premature == 0, "the address changed before the status stage");

      setup_valid = 1'b0; status_ack = 1'b0; bus_reset = 1'b0;
      tok_valid   = 1'b0; data_stage = 1'b0;
    end
  endtask

  task idle; begin step(0,8'd0,16'd0,0,0,0,0,T_SOF,7'd0); end endtask

  task reset_dut;
    begin
      rst_n = 1'b0;
      setup_valid = 0; status_ack = 0; bus_reset = 0; tok_valid = 0;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      s_addr = 7'd0; s_pend = 7'd0; s_pend_valid = 1'b0;
      s_conf = 1'b0; s_conf_pend = 1'b0; s_have_addr = 1'b0;
      x_setup = 0; x_chg = 0; x_rst = 0; x_wrong = 0; x_drop = 0;
      @(posedge clk); #1;
    end
  endtask

  // ---- drive the device into a named state, the way the host would ----
  //
  // Never by forcing. A state reached by poking registers is not a state
  // the design can actually be in.
  task goto_state(input [2:0] want);
    begin
      reset_dut;
      if (want == S_DEFAULT) begin
        // already there
      end else if (want == S_ADDR_PEND) begin
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
      end else if (want == S_ADDRESSED) begin
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
        step(0,8'd0,16'd0,1,0,0,0,T_SOF,7'd0);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
      end else if (want == S_CONFIG_PEND) begin
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
        step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
      end else begin // S_CONFIGURED
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
        step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
      end
    end
  endtask

  integer si, qi, ki, bi, k;
  reg [6:0] ea, eb;
  reg [7:0] reqs [0:3];

  initial begin
    for (ri = 0; ri < 80; ri = ri + 1)  reach[ri]  = 1'b0;
    for (ai2 = 0; ai2 < 128; ai2 = ai2 + 1) areach[ai2] = 1'b0;
    reqs[0] = R_GET_DESCRIPTOR; reqs[1] = R_SET_ADDRESS;
    reqs[2] = R_SET_CONFIG;     reqs[3] = R_OTHER;
    seed = 32'd27003;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every request in every
    //  state, with and without a status stage, with and without a
    //  bus reset. 5 x 4 x 2 x 2 = 80.
    // =============================================================
    for (si = 0; si < 5; si = si + 1)
    for (qi = 0; qi < 4; qi = qi + 1)
    for (ki = 0; ki < 2; ki = ki + 1)
    for (bi = 0; bi < 2; bi = bi + 1) begin
      goto_state(si[2:0]);
      step(1, reqs[qi], 16'd42, 0, ki[0], bi[0], 1, T_SETUP, dev_addr);
      idle;
      idle;
      ri = (si * 16) + (qi * 4) + (ki * 2) + bi;
      reach[ri] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED, EXHAUSTIVE) -- every address 0..127.
    //
    //  SET_ADDRESS to each value in turn, checking at every step that
    //  the old address is still live until the status stage. One
    //  address proves nothing: the interesting values are 0 (which is
    //  legal and means "go back to default") and 127 (the maximum).
    // =============================================================
    for (k = 0; k < 128; k = k + 1) begin
      reset_dut;
      step(1, R_SET_ADDRESS, {9'd0, k[6:0]}, 0, 0, 0, 1, T_SETUP, 7'd0);
      // the device is STILL address 0 here, for as long as the host takes
      idle;
      idle;
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      // and only now is it address k
      idle;
      areach[k] = 1'b1;
    end

    // =============================================================
    //  PHASE 3 (DIRECTED) -- a bus reset from every state.
    //
    //  A reset returns the device to DEFAULT and address 0 from
    //  anywhere. This is the recovery path the whole protocol leans on.
    // =============================================================
    for (si = 0; si < 5; si = si + 1) begin
      goto_state(si[2:0]);
      step(0, 8'd0, 16'd0, 0, 0, 1, 0, T_SOF, 7'd0);
      ck(dev_addr === 7'd0, "a bus reset did not return the device to address 0");
      ck(state    === S_DEFAULT, "a bus reset did not return the device to DEFAULT");
      ck(configured === 1'b0, "a bus reset left the device configured");
      idle;
    end

    // =============================================================
    //  PHASE 4 (DIRECTED) -- a status stage with nothing outstanding.
    //
    //  A stray ACK must change nothing. This is the mirror image of the
    //  central rule and it is where a design that keys off the wrong
    //  event shows up.
    // =============================================================
    for (si = 0; si < 5; si = si + 1) begin
      goto_state(si[2:0]);
      for (k = 0; k < 4; k = k + 1)
        step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
    end

    // =============================================================
    //  PHASE 4b (DIRECTED, EXHAUSTIVE) -- every request COMPLETED.
    //
    //  Phase 1 issues a request in every state but never follows it with
    //  a status stage, so nothing it does can be observed: C3 (accepting
    //  SET_CONFIGURATION in DEFAULT) and C5 (staying configured across a
    //  re-address) both had a directed score of exactly ZERO.
    //
    //  A request that is never completed changes nothing, so a phase that
    //  never completes one tests nothing. 5 states x 4 requests.
    // =============================================================
    for (si = 0; si < 5; si = si + 1)
    for (qi = 0; qi < 4; qi = qi + 1) begin
      goto_state(si[2:0]);
      step(1, reqs[qi], 16'd33, 0, 0, 0, 1, T_SETUP, dev_addr);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);   // complete it
      idle;
      // and a second completion, which must change nothing further
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
      idle;
    end

    // =============================================================
    //  PHASE 4c (DIRECTED, EXHAUSTIVE) -- a REAL enumeration, eight times.
    //
    //  This is the chapter's question executed as stimulus: the whole
    //  lifecycle from attach to configured, including the two steps that
    //  only make sense in sequence -- a SET_CONFIGURATION refused because
    //  no address has been established, and a re-address that must
    //  UNCONFIGURE the device.
    //
    //  Phase 4b reaches each of those situations exactly once, which gave
    //  three mutations directed scores of 8, 4 and 4. Eight passes over
    //  eight different address pairs turns luck into arithmetic.
    // =============================================================
    for (k = 0; k < 8; k = k + 1) begin
      ea = 7'd1   + k[6:0] * 7'd7;
      eb = 7'd120 - k[6:0] * 7'd7;
      reset_dut;

      // 1. SET_CONFIGURATION before any address has been established.
      //    Refused -- and refused even though the host completes the
      //    transfer, which is the part that makes C3 observable.
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(configured === 1'b0, "configured without ever having an address");
      ck(addressed  === 1'b0, "addressed without a completed SET_ADDRESS");

      // 2. GET_DESCRIPTOR at address 0. Legal, and how the host learns
      //    the packet size it must use for everything after this.
      step(1, R_GET_DESCRIPTOR, 16'd0, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(dev_addr === 7'd0, "GET_DESCRIPTOR moved the device off address 0");

      // 2b. A SET_ADDRESS that is ABANDONED before its status stage.
      //
      //     The host asks, then changes its mind and asks for a
      //     configuration instead. Two things must hold: the device is
      //     still NOT addressed while the request is merely outstanding,
      //     and the SET_CONFIGURATION is therefore refused. A device that
      //     marks itself addressed when the request ARRIVES passes every
      //     other check in this bench and fails both of these.
      step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
      ck(dev_addr  === 7'd0, "address changed before the status stage");
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(configured === 1'b0, "configured off an abandoned SET_ADDRESS");
      ck(dev_addr   === 7'd0, "an abandoned SET_ADDRESS still took effect");
      ck(addressed  === 1'b0, "addressed off an abandoned SET_ADDRESS");

      // 3. SET_ADDRESS. The old address stays live until the status stage.
      step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      ck(dev_addr === 7'd0, "address changed before the status stage");
      idle;
      ck(dev_addr === 7'd0, "address changed before the status stage");
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(dev_addr  === ea,   "address did not take effect after the status stage");
      ck(addressed === 1'b1, "device not addressed after a completed SET_ADDRESS");

      // 4. Now SET_CONFIGURATION is meaningful.
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, ea);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
      idle;
      ck(configured === 1'b1, "device did not configure when it should have");

      // 5. Re-address. The configuration survives until the status stage
      //    and must be gone immediately after it.
      step(1, R_SET_ADDRESS, {9'd0, eb}, 0, 0, 0, 1, T_SETUP, ea);
      idle;
      ck(dev_addr   === ea,   "address changed before the status stage");
      ck(configured === 1'b1, "configuration dropped before the re-address completed");
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
      idle;
      ck(dev_addr   === eb,   "re-address did not take effect");
      ck(configured === 1'b0, "device stayed configured across a re-address");

      // 6. And it can be configured again at the new address.
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, eb);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, eb);
      idle;
      ck(configured === 1'b1, "device could not be reconfigured after a re-address");
    end

    // =============================================================
    //  PHASE 5 (RANDOM) -- a host doing all of it in any order.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 30000; k = k + 1) begin
      step((urand(0) % 4) == 0,                 // setup_valid
           reqs[urand(0) % 4],
           {9'd0, urand(0)} & 16'h007F,
           (urand(0) % 3) == 0,                  // data_stage
           (urand(0) % 4) == 0,                  // status_ack
           (urand(0) % 200) == 0,                // bus_reset, rare
           (urand(0) % 2) == 0,                  // tok_valid
           urand(0) % 4,
           ((urand(0) % 3) == 0) ? (urand(0) & 7'h7F) : dev_addr);
    end
`endif

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

    $display("steps=%0d checks=%0d reach=%0d/80 addrs=%0d/128 errors=%0d",
             steps, checks, n_reach, n_areach, errors);
    $display("[enum] setups=%0d addr_changes=%0d resets=%0d wrong_addr=%0d dropped=%0d",
             n_setup, n_addr_change, n_reset_to_default, n_wrong_addr,
             n_dropped_request);
    $display("[the whole point] premature address switches = %0d", n_premature);
    if (n_reach != 80 || n_areach != 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_enum_fsm.
//
//  The shadow model does not mirror the FSM. It answers a different
//  question: "what is the last address a COMPLETED SET_ADDRESS status
//  stage established, since the most recent bus reset?" -- a claim about
//  completed transfers rather than about states, so a defect in the state
//  machine cannot hide inside an identical state machine.
// =====================================================================
`timescale 1ns/1ps
module tb_en_sv;

  localparam [1:0] T_OUT = 2'd0, T_IN = 2'd1, T_SOF = 2'd2, T_SETUP = 2'd3;
  localparam [7:0] R_GET_DESCRIPTOR = 8'h06, R_SET_ADDRESS = 8'h05,
                   R_SET_CONFIG     = 8'h09, R_OTHER       = 8'h0B;
  localparam [2:0] S_DEFAULT = 3'd0, S_ADDR_PEND = 3'd1, S_ADDRESSED = 3'd2,
                   S_CONFIG_PEND = 3'd3, S_CONFIGURED = 3'd4;

  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       setup_valid = 1'b0;
  logic [7:0] req = 8'd0;
  logic [15:0] val = 16'd0;
  logic       data_stage = 1'b0;
  logic       status_ack = 1'b0;
  logic       bus_reset  = 1'b0;

  logic [6:0] dev_addr;
  logic [2:0] state;
  logic       configured, addressed;
  logic [31:0] n_setup, n_addr_change, n_early_switch,
               n_reset_to_default, n_wrong_addr, n_dropped_request;

  usb_enum_fsm dut (
    .clk(clk), .rst_n(rst_n),
    .tok_valid(tok_valid), .tok_pid(tok_pid), .tok_addr(tok_addr),
    .setup_valid(setup_valid), .req(req), .val(val),
    .data_stage(data_stage), .status_ack(status_ack),
    .bus_reset(bus_reset),
    .dev_addr(dev_addr), .state(state),
    .configured(configured), .addressed(addressed),
    .n_setup(n_setup), .n_addr_change(n_addr_change),
    .n_early_switch(n_early_switch),
    .n_reset_to_default(n_reset_to_default), .n_wrong_addr(n_wrong_addr),
    .n_dropped_request(n_dropped_request)
  );

  always #5 clk = ~clk;

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

  integer seed;

  // ---- $random is SIGNED: mask the sign bit before any modulo ----
  function automatic logic [31:0] urand(bit dummy);
    return $random(seed) & 32'h3FFF_FFFF;
  endfunction

  // ---- the shadow: a claim about completed transfers ----
  logic [6:0] s_addr;        // address established by the last completed SET_ADDRESS
  logic [6:0] s_pend;        // address a SETUP has requested but not yet established
  logic      s_pend_valid;  // a SET_ADDRESS is outstanding
  logic      s_conf;        // configured
  logic      s_conf_pend;
  logic      s_have_addr;  // a SET_ADDRESS status stage has completed
  // Snapshots of the outstanding-request state as it stood BEFORE this
  // cycle. The design decides the status stage from its REGISTERED state,
  // so a shadow using blocking assignments would apply a SET_ADDRESS in
  // the same cycle its SETUP arrived -- which is precisely the defect
  // under test, asserted by the model instead of checked.
  logic      p_pend_valid, p_conf_pend, p_have_addr;
  logic [6:0] p_pend;
  logic [31:0] x_setup, x_chg, x_rst, x_wrong, x_drop;

  // ---- the headline counter ----
  //
  // Every cycle in which the live address differed from the address the
  // last COMPLETED status stage established. On a correct device this is
  // structurally impossible, and the run publishes the number.
  integer n_premature = 0;

  // ---- exhaustive reach over (state, request, ack, reset) ----
  logic reach [0:79];
  integer ri, n_reach;
  // ---- and over every address SET_ADDRESS can name ----
  logic areach [0:127];
  integer ai2, n_areach;

  task ck(input logic 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.
  // ---------------------------------------------------------------
  task step(input logic sv, input logic [7:0] rq, input logic [15:0] vl,
            input logic ds, input logic sa, input logic br,
            input logic tv, input logic [1:0] tp, input logic [6:0] ta);
    logic [6:0] e_addr;
    logic     e_conf;
    begin
      setup_valid = sv;  req = rq;  val = vl;
      data_stage  = ds;  status_ack = sa;  bus_reset = br;
      tok_valid   = tv;  tok_pid = tp;  tok_addr = ta;

      // ---- advance the shadow to what SHOULD hold after this edge ----
      if (br) begin
        s_addr = 7'd0;  s_pend = 7'd0;  s_pend_valid = 1'b0;
        s_conf = 1'b0;  s_conf_pend = 1'b0;  s_have_addr = 1'b0;
        x_rst  = x_rst + 1;
      end else begin
        if (tv && (tp != T_SOF) && (ta != s_addr)) x_wrong = x_wrong + 1;

        // Snapshot of what was outstanding BEFORE this cycle. The status
        // stage resolves THAT, never a request arriving on the same edge.
        p_pend_valid = s_pend_valid;
        p_pend       = s_pend;
        p_conf_pend  = s_conf_pend;
        // have_addr is snapshotted too: the device gates SET_CONFIG on its
        // REGISTERED value, so a SET_ADDRESS completing on this same edge
        // does not yet authorise a SET_CONFIG arriving with it.
        p_have_addr  = s_have_addr;

        // ---- status stage first, exactly as the device orders it ----
        if (sa) begin
          s_pend_valid = 1'b0;
          s_conf_pend  = 1'b0;
          if (p_pend_valid) begin
            s_addr = p_pend;  s_have_addr = 1'b1;
            s_conf = 1'b0;                     // re-addressing unconfigures
            x_chg  = x_chg + 1;
          end else if (p_conf_pend) begin
            s_conf = 1'b1;
          end
        end

        // ---- then the new request, which displaces whatever was there ----
        if (sv) begin
          x_setup = x_setup + 1;
          if (p_pend_valid || p_conf_pend) begin
            if (!sa) x_drop = x_drop + 1;
          end
          if (rq == R_SET_ADDRESS) begin
            s_pend = vl[6:0];  s_pend_valid = 1'b1;  s_conf_pend = 1'b0;
          end else if (rq == R_SET_CONFIG) begin
            s_pend_valid = 1'b0;
            s_conf_pend  = p_have_addr;
          end else begin
            s_pend_valid = 1'b0;  s_conf_pend = 1'b0;
          end
        end
      end

      e_addr = s_addr;
      e_conf = s_conf;

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

      // ---- PROPERTY 1: the live address is the last COMPLETED one ----
      if (dev_addr !== e_addr) n_premature = n_premature + 1;
      ck(dev_addr === e_addr, "live address is not the last completed address");

      // ---- PROPERTY 2: configured only after its own status stage ----
      ck(configured === e_conf, "configured flag disagrees");

      // ---- PROPERTY 3: a pending SET_ADDRESS has NOT taken effect ----
      //
      // The rule stated directly: while a SET_ADDRESS is outstanding the
      // device must still be answering at the address it had before.
      if (s_pend_valid && (s_pend != s_addr))
        ck(dev_addr !== s_pend,
           "device switched to the new address before the status stage");

      // ---- PROPERTY 4: counters agree with an independent tally ----
      ck(n_setup            === x_setup, "SETUP count disagrees");
      ck(n_addr_change      === x_chg,   "address-change count disagrees");
      ck(n_reset_to_default === x_rst,   "reset count disagrees");
      ck(n_wrong_addr       === x_wrong, "wrong-address count disagrees");
      ck(n_dropped_request  === x_drop,  "dropped-request count disagrees");

      // ---- PROPERTY 5: the design's own early-switch counter is 0 ----
      ck(n_early_switch === 32'd0, "the design detected its own early switch");
      ck(n_premature == 0, "the address changed before the status stage");

      setup_valid = 1'b0; status_ack = 1'b0; bus_reset = 1'b0;
      tok_valid   = 1'b0; data_stage = 1'b0;
    end
  endtask

  task idle; begin step(0,8'd0,16'd0,0,0,0,0,T_SOF,7'd0); end endtask

  task reset_dut;
    begin
      rst_n = 1'b0;
      setup_valid = 0; status_ack = 0; bus_reset = 0; tok_valid = 0;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      s_addr = 7'd0; s_pend = 7'd0; s_pend_valid = 1'b0;
      s_conf = 1'b0; s_conf_pend = 1'b0; s_have_addr = 1'b0;
      x_setup = 0; x_chg = 0; x_rst = 0; x_wrong = 0; x_drop = 0;
      @(posedge clk); #1;
    end
  endtask

  // ---- drive the device into a named state, the way the host would ----
  //
  // Never by forcing. A state reached by poking registers is not a state
  // the design can actually be in.
  task goto_state(input logic [2:0] want);
    begin
      reset_dut;
      if (want == S_DEFAULT) begin
        // already there
      end else if (want == S_ADDR_PEND) begin
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
      end else if (want == S_ADDRESSED) begin
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
        step(0,8'd0,16'd0,1,0,0,0,T_SOF,7'd0);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
      end else if (want == S_CONFIG_PEND) begin
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
        step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
      end else begin // S_CONFIGURED
        step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
        step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
        step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
      end
    end
  endtask

  integer si, qi, ki, bi, k;
  logic [6:0] ea, eb;
  logic [7:0] reqs [0:3];

  initial begin
    for (ri = 0; ri < 80; ri = ri + 1)  reach[ri]  = 1'b0;
    for (ai2 = 0; ai2 < 128; ai2 = ai2 + 1) areach[ai2] = 1'b0;
    reqs[0] = R_GET_DESCRIPTOR; reqs[1] = R_SET_ADDRESS;
    reqs[2] = R_SET_CONFIG;     reqs[3] = R_OTHER;
    seed = 32'd27003;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every request in every
    //  state, with and without a status stage, with and without a
    //  bus reset. 5 x 4 x 2 x 2 = 80.
    // =============================================================
    for (si = 0; si < 5; si = si + 1)
    for (qi = 0; qi < 4; qi = qi + 1)
    for (ki = 0; ki < 2; ki = ki + 1)
    for (bi = 0; bi < 2; bi = bi + 1) begin
      goto_state(si[2:0]);
      step(1, reqs[qi], 16'd42, 0, ki[0], bi[0], 1, T_SETUP, dev_addr);
      idle;
      idle;
      ri = (si * 16) + (qi * 4) + (ki * 2) + bi;
      reach[ri] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED, EXHAUSTIVE) -- every address 0..127.
    //
    //  SET_ADDRESS to each value in turn, checking at every step that
    //  the old address is still live until the status stage. One
    //  address proves nothing: the interesting values are 0 (which is
    //  legal and means "go back to default") and 127 (the maximum).
    // =============================================================
    for (k = 0; k < 128; k = k + 1) begin
      reset_dut;
      step(1, R_SET_ADDRESS, {9'd0, k[6:0]}, 0, 0, 0, 1, T_SETUP, 7'd0);
      // the device is STILL address 0 here, for as long as the host takes
      idle;
      idle;
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      // and only now is it address k
      idle;
      areach[k] = 1'b1;
    end

    // =============================================================
    //  PHASE 3 (DIRECTED) -- a bus reset from every state.
    //
    //  A reset returns the device to DEFAULT and address 0 from
    //  anywhere. This is the recovery path the whole protocol leans on.
    // =============================================================
    for (si = 0; si < 5; si = si + 1) begin
      goto_state(si[2:0]);
      step(0, 8'd0, 16'd0, 0, 0, 1, 0, T_SOF, 7'd0);
      ck(dev_addr === 7'd0, "a bus reset did not return the device to address 0");
      ck(state    === S_DEFAULT, "a bus reset did not return the device to DEFAULT");
      ck(configured === 1'b0, "a bus reset left the device configured");
      idle;
    end

    // =============================================================
    //  PHASE 4 (DIRECTED) -- a status stage with nothing outstanding.
    //
    //  A stray ACK must change nothing. This is the mirror image of the
    //  central rule and it is where a design that keys off the wrong
    //  event shows up.
    // =============================================================
    for (si = 0; si < 5; si = si + 1) begin
      goto_state(si[2:0]);
      for (k = 0; k < 4; k = k + 1)
        step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
    end

    // =============================================================
    //  PHASE 4b (DIRECTED, EXHAUSTIVE) -- every request COMPLETED.
    //
    //  Phase 1 issues a request in every state but never follows it with
    //  a status stage, so nothing it does can be observed: C3 (accepting
    //  SET_CONFIGURATION in DEFAULT) and C5 (staying configured across a
    //  re-address) both had a directed score of exactly ZERO.
    //
    //  A request that is never completed changes nothing, so a phase that
    //  never completes one tests nothing. 5 states x 4 requests.
    // =============================================================
    for (si = 0; si < 5; si = si + 1)
    for (qi = 0; qi < 4; qi = qi + 1) begin
      goto_state(si[2:0]);
      step(1, reqs[qi], 16'd33, 0, 0, 0, 1, T_SETUP, dev_addr);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);   // complete it
      idle;
      // and a second completion, which must change nothing further
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
      idle;
    end

    // =============================================================
    //  PHASE 4c (DIRECTED, EXHAUSTIVE) -- a REAL enumeration, eight times.
    //
    //  This is the chapter's question executed as stimulus: the whole
    //  lifecycle from attach to configured, including the two steps that
    //  only make sense in sequence -- a SET_CONFIGURATION refused because
    //  no address has been established, and a re-address that must
    //  UNCONFIGURE the device.
    //
    //  Phase 4b reaches each of those situations exactly once, which gave
    //  three mutations directed scores of 8, 4 and 4. Eight passes over
    //  eight different address pairs turns luck into arithmetic.
    // =============================================================
    for (k = 0; k < 8; k = k + 1) begin
      ea = 7'd1   + k[6:0] * 7'd7;
      eb = 7'd120 - k[6:0] * 7'd7;
      reset_dut;

      // 1. SET_CONFIGURATION before any address has been established.
      //    Refused -- and refused even though the host completes the
      //    transfer, which is the part that makes C3 observable.
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(configured === 1'b0, "configured without ever having an address");
      ck(addressed  === 1'b0, "addressed without a completed SET_ADDRESS");

      // 2. GET_DESCRIPTOR at address 0. Legal, and how the host learns
      //    the packet size it must use for everything after this.
      step(1, R_GET_DESCRIPTOR, 16'd0, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(dev_addr === 7'd0, "GET_DESCRIPTOR moved the device off address 0");

      // 2b. A SET_ADDRESS that is ABANDONED before its status stage.
      //
      //     The host asks, then changes its mind and asks for a
      //     configuration instead. Two things must hold: the device is
      //     still NOT addressed while the request is merely outstanding,
      //     and the SET_CONFIGURATION is therefore refused. A device that
      //     marks itself addressed when the request ARRIVES passes every
      //     other check in this bench and fails both of these.
      step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
      ck(dev_addr  === 7'd0, "address changed before the status stage");
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(configured === 1'b0, "configured off an abandoned SET_ADDRESS");
      ck(dev_addr   === 7'd0, "an abandoned SET_ADDRESS still took effect");
      ck(addressed  === 1'b0, "addressed off an abandoned SET_ADDRESS");

      // 3. SET_ADDRESS. The old address stays live until the status stage.
      step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
      idle;
      ck(dev_addr === 7'd0, "address changed before the status stage");
      idle;
      ck(dev_addr === 7'd0, "address changed before the status stage");
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
      idle;
      ck(dev_addr  === ea,   "address did not take effect after the status stage");
      ck(addressed === 1'b1, "device not addressed after a completed SET_ADDRESS");

      // 4. Now SET_CONFIGURATION is meaningful.
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, ea);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
      idle;
      ck(configured === 1'b1, "device did not configure when it should have");

      // 5. Re-address. The configuration survives until the status stage
      //    and must be gone immediately after it.
      step(1, R_SET_ADDRESS, {9'd0, eb}, 0, 0, 0, 1, T_SETUP, ea);
      idle;
      ck(dev_addr   === ea,   "address changed before the status stage");
      ck(configured === 1'b1, "configuration dropped before the re-address completed");
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
      idle;
      ck(dev_addr   === eb,   "re-address did not take effect");
      ck(configured === 1'b0, "device stayed configured across a re-address");

      // 6. And it can be configured again at the new address.
      step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, eb);
      idle;
      step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, eb);
      idle;
      ck(configured === 1'b1, "device could not be reconfigured after a re-address");
    end

    // =============================================================
    //  PHASE 5 (RANDOM) -- a host doing all of it in any order.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 30000; k = k + 1) begin
      step((urand(0) % 4) == 0,                 // setup_valid
           reqs[urand(0) % 4],
           {9'd0, urand(0)} & 16'h007F,
           (urand(0) % 3) == 0,                  // data_stage
           (urand(0) % 4) == 0,                  // status_ack
           (urand(0) % 200) == 0,                // bus_reset, rare
           (urand(0) % 2) == 0,                  // tok_valid
           urand(0) % 4,
           ((urand(0) % 3) == 0) ? (urand(0) & 7'h7F) : dev_addr);
    end
`endif

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

    $display("steps=%0d checks=%0d reach=%0d/80 addrs=%0d/128 errors=%0d",
             steps, checks, n_reach, n_areach, errors);
    $display("[enum] setups=%0d addr_changes=%0d resets=%0d wrong_addr=%0d dropped=%0d",
             n_setup, n_addr_change, n_reset_to_default, n_wrong_addr,
             n_dropped_request);
    $display("[the whole point] premature address switches = %0d", n_premature);
    if (n_reach != 80 || n_areach != 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_enum_fsm (VHDL-2008).
--
--  Same shadow model and the same five phases. The random source is an
--  xorshift unrelated to Icarus's generator, so the VHDL column of the
--  mutation table is a second opinion rather than a third copy.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.en_pkg.all;

entity tb_en_vhdl is
  -- No preprocessor: the directed/random split is an elaboration generic.
  generic (DIRECTED_ONLY : boolean := false);
end entity;

architecture sim of tb_en_vhdl is
  constant REQ_OTHER : std_logic_vector(7 downto 0) := x"0B";

  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 setup_valid : std_logic := '0';
  signal req         : std_logic_vector(7 downto 0) := (others => '0');
  signal val         : std_logic_vector(15 downto 0) := (others => '0');
  signal data_stage  : std_logic := '0';
  signal status_ack  : std_logic := '0';
  signal bus_reset   : std_logic := '0';

  signal dev_addr    : std_logic_vector(6 downto 0);
  signal state       : std_logic_vector(2 downto 0);
  signal configured  : std_logic;
  signal addressed   : std_logic;
  signal n_setup, n_addr_change, n_early_switch,
         n_reset_to_default, n_wrong_addr, n_dropped_request
                     : std_logic_vector(31 downto 0);

  signal done : boolean := false;
begin

  dut : entity work.usb_enum_fsm
    port map (
      clk => clk, rst_n => rst_n,
      tok_valid => tok_valid, tok_pid => tok_pid, tok_addr => tok_addr,
      setup_valid => setup_valid, req => req, val => val,
      data_stage => data_stage, status_ack => status_ack,
      bus_reset => bus_reset,
      dev_addr => dev_addr, state => state,
      configured => configured, addressed => addressed,
      n_setup => n_setup, n_addr_change => n_addr_change,
      n_early_switch => n_early_switch,
      n_reset_to_default => n_reset_to_default,
      n_wrong_addr => n_wrong_addr,
      n_dropped_request => n_dropped_request);

  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 n_premature: natural := 0;

    variable s_addr, s_pend, p_pend : std_logic_vector(6 downto 0)
      := (others => '0');
    variable s_pend_valid, s_conf, s_conf_pend, s_have_addr : std_logic := '0';
    variable p_pend_valid, p_conf_pend, p_have_addr         : std_logic := '0';
    variable x_setup, x_chg, x_rst, x_wrong, x_drop : natural := 0;

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

    variable rnd : unsigned(31 downto 0) := x"0004C2F9";
    variable ln  : line;

    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
      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(sv : std_logic;
                   rq : std_logic_vector(7 downto 0);
                   vl : std_logic_vector(15 downto 0);
                   ds : std_logic; sa : std_logic; br : std_logic;
                   tv : std_logic;
                   tp : std_logic_vector(1 downto 0);
                   ta : std_logic_vector(6 downto 0)) is
      variable e_addr : std_logic_vector(6 downto 0);
      variable e_conf : std_logic;
    begin
      setup_valid <= sv;  req <= rq;  val <= vl;
      data_stage  <= ds;  status_ack <= sa;  bus_reset <= br;
      tok_valid   <= tv;  tok_pid <= tp;  tok_addr <= ta;

      if br = '1' then
        s_addr := (others => '0');  s_pend := (others => '0');
        s_pend_valid := '0';  s_conf := '0';  s_conf_pend := '0';
        s_have_addr := '0';
        x_rst := x_rst + 1;
      else
        if tv = '1' and tp /= TOK_SOF and ta /= s_addr then
          x_wrong := x_wrong + 1;
        end if;

        -- snapshot of what was outstanding BEFORE this cycle
        p_pend_valid := s_pend_valid;
        p_pend       := s_pend;
        p_conf_pend  := s_conf_pend;
        -- have_addr is snapshotted too: the device gates SET_CONFIG on its
        -- REGISTERED value, so a SET_ADDRESS completing on this same edge
        -- does not yet authorise a SET_CONFIG arriving with it.
        p_have_addr  := s_have_addr;

        -- status stage first, exactly as the device orders it
        if sa = '1' then
          s_pend_valid := '0';
          s_conf_pend  := '0';
          if p_pend_valid = '1' then
            s_addr := p_pend;  s_have_addr := '1';
            s_conf := '0';                    -- re-addressing unconfigures
            x_chg  := x_chg + 1;
          elsif p_conf_pend = '1' then
            s_conf := '1';
          end if;
        end if;

        -- then the new request, which displaces whatever was there
        if sv = '1' then
          x_setup := x_setup + 1;
          if (p_pend_valid = '1' or p_conf_pend = '1') and sa = '0' then
            x_drop := x_drop + 1;
          end if;
          if rq = REQ_SET_ADDRESS then
            s_pend := vl(6 downto 0);  s_pend_valid := '1';  s_conf_pend := '0';
          elsif rq = REQ_SET_CONFIG then
            s_pend_valid := '0';
            s_conf_pend  := p_have_addr;
          else
            s_pend_valid := '0';  s_conf_pend := '0';
          end if;
        end if;
      end if;

      e_addr := s_addr;
      e_conf := s_conf;

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

      if dev_addr /= e_addr then n_premature := n_premature + 1; end if;
      ck(dev_addr = e_addr, "live address is not the last completed address");
      ck((configured = '1') = (e_conf = '1'), "configured flag disagrees");

      if s_pend_valid = '1' and s_pend /= s_addr then
        ck(dev_addr /= s_pend,
           "device switched to the new address before the status stage");
      end if;

      ck(to_integer(unsigned(n_setup))            = x_setup, "SETUP count disagrees");
      ck(to_integer(unsigned(n_addr_change))      = x_chg,   "address-change count disagrees");
      ck(to_integer(unsigned(n_reset_to_default)) = x_rst,   "reset count disagrees");
      ck(to_integer(unsigned(n_wrong_addr))       = x_wrong, "wrong-address count disagrees");
      ck(to_integer(unsigned(n_dropped_request))  = x_drop,  "dropped-request count disagrees");
      ck(to_integer(unsigned(n_early_switch))     = 0,
         "the design detected its own early switch");
      ck(n_premature = 0, "the address changed before the status stage");

      setup_valid <= '0';  status_ack <= '0';  bus_reset <= '0';
      tok_valid   <= '0';  data_stage <= '0';
    end procedure;

    procedure idle is
    begin
      step('0', x"00", x"0000", '0', '0', '0', '0', TOK_SOF, "0000000");
    end procedure;

    procedure reset_dut is
    begin
      rst_n <= '0';
      setup_valid <= '0';  status_ack <= '0';  bus_reset <= '0';
      tok_valid   <= '0';
      wait until rising_edge(clk);
      wait until rising_edge(clk);
      rst_n <= '1';
      s_addr := (others => '0');  s_pend := (others => '0');
      s_pend_valid := '0';  s_conf := '0';  s_conf_pend := '0';
      s_have_addr := '0';
      x_setup := 0; x_chg := 0; x_rst := 0; x_wrong := 0; x_drop := 0;
      wait until rising_edge(clk);
      wait for 1 ns;
    end procedure;

    -- Drive the device into a named state the way the host would. Never by
    -- forcing: a state reached by poking registers is not a state the
    -- design can actually be in.
    procedure goto_state(want : natural) is
    begin
      reset_dut;
      if want = 1 then
        step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
      elsif want = 2 then
        step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
        step('0', x"00", x"0000", '1', '0', '0', '0', TOK_SOF, "0000000");
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      elsif want = 3 then
        step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
        step('1', REQ_SET_CONFIG,  x"0001", '0', '0', '0', '1', TOK_SETUP, "0000111");
      elsif want = 4 then
        step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
        step('1', REQ_SET_CONFIG,  x"0001", '0', '0', '0', '1', TOK_SETUP, "0000111");
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      end if;
    end procedure;

    type req_arr is array (0 to 3) of std_logic_vector(7 downto 0);
    constant reqs : req_arr := (REQ_GET_DESCRIPTOR, REQ_SET_ADDRESS,
                                REQ_SET_CONFIG, REQ_OTHER);
    variable ri  : natural;
    variable sa2, br2 : std_logic;
    -- VHDL has no inline declare block inside a process body, so the
    -- random phase's temporaries live here rather than where they are used.
    variable sv3, ds3, sa3, br3, tv3 : std_logic := '0';
    variable ta3 : std_logic_vector(6 downto 0);
    variable ea, eb : std_logic_vector(6 downto 0);
  begin
    -- PHASE 1 (DIRECTED, EXHAUSTIVE) -- 5 states x 4 requests x ack x reset
    for si in 0 to 4 loop
      for qi in 0 to 3 loop
        for ki in 0 to 1 loop
          for bi in 0 to 1 loop
            goto_state(si);
            if ki = 1 then sa2 := '1'; else sa2 := '0'; end if;
            if bi = 1 then br2 := '1'; else br2 := '0'; end if;
            step('1', reqs(qi), x"002A", '0', sa2, br2, '1', TOK_SETUP, dev_addr);
            idle;
            idle;
            ri := si*16 + qi*4 + ki*2 + bi;
            reach(ri) := '1';
          end loop;
        end loop;
      end loop;
    end loop;

    -- PHASE 2 (DIRECTED, EXHAUSTIVE) -- every address 0..127
    for k in 0 to 127 loop
      reset_dut;
      step('1', REQ_SET_ADDRESS, std_logic_vector(to_unsigned(k, 16)),
           '0', '0', '0', '1', TOK_SETUP, "0000000");
      -- still address 0 here, for as long as the host takes
      idle; idle; idle;
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      -- and only now is it address k
      idle;
      areach(k) := '1';
    end loop;

    -- PHASE 3 (DIRECTED) -- a bus reset from every state
    for si in 0 to 4 loop
      goto_state(si);
      step('0', x"00", x"0000", '0', '0', '1', '0', TOK_SOF, "0000000");
      ck(dev_addr = std_logic_vector'("0000000"),
         "a bus reset did not return the device to address 0");
      ck(state = std_logic_vector'("000"),
         "a bus reset did not return the device to DEFAULT");
      ck(configured = '0', "a bus reset left the device configured");
      idle;
    end loop;

    -- PHASE 4 (DIRECTED) -- a status stage with nothing outstanding
    for si in 0 to 4 loop
      goto_state(si);
      for k in 0 to 3 loop
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, dev_addr);
      end loop;
    end loop;

    -- PHASE 4b (DIRECTED, EXHAUSTIVE) -- every request COMPLETED.
    --
    -- Phase 1 issues a request in every state but never follows it with a
    -- status stage, so nothing it does can be observed: two mutations had
    -- a directed score of exactly zero. A request that is never completed
    -- changes nothing, so a phase that never completes one tests nothing.
    for si in 0 to 4 loop
      for qi in 0 to 3 loop
        goto_state(si);
        step('1', reqs(qi), x"0021", '0', '0', '0', '1', TOK_SETUP, dev_addr);
        idle;
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, dev_addr);
        idle;
        -- a second completion, which must change nothing further
        step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, dev_addr);
        idle;
      end loop;
    end loop;

    -- PHASE 4c (DIRECTED, EXHAUSTIVE) -- a REAL enumeration, eight times.
    --
    -- The chapter's question executed as stimulus: the whole lifecycle from
    -- attach to configured, including the two steps that only make sense in
    -- sequence -- a SET_CONFIGURATION refused because no address has been
    -- established, and a re-address that must UNCONFIGURE the device.
    for k in 0 to 7 loop
      ea := std_logic_vector(to_unsigned(1 + k*7, 7));
      eb := std_logic_vector(to_unsigned(120 - k*7, 7));
      reset_dut;

      -- 1. SET_CONFIGURATION before any address is established: refused,
      --    even though the host completes the transfer.
      step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, "0000000");
      idle;
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      idle;
      ck(configured = '0', "configured without ever having an address");
      ck(addressed  = '0', "addressed without a completed SET_ADDRESS");

      -- 2. GET_DESCRIPTOR at address 0: legal.
      step('1', REQ_GET_DESCRIPTOR, x"0000", '0', '0', '0', '1', TOK_SETUP, "0000000");
      idle;
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      idle;
      ck(dev_addr = std_logic_vector'("0000000"),
         "GET_DESCRIPTOR moved the device off address 0");

      -- 2b. A SET_ADDRESS that is ABANDONED before its status stage. The
      --     device is still NOT addressed while the request is merely
      --     outstanding, so the SET_CONFIGURATION is refused. A device that
      --     marks itself addressed when the request ARRIVES passes every
      --     other check here and fails both of these.
      step('1', REQ_SET_ADDRESS, "000000000" & ea, '0', '0', '0', '1', TOK_SETUP, "0000000");
      idle;
      ck(addressed = '0', "device claimed to be addressed before the status stage");
      ck(dev_addr = std_logic_vector'("0000000"), "address changed before the status stage");
      step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, "0000000");
      idle;
      ck(addressed = '0', "device claimed to be addressed before the status stage");
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      idle;
      ck(configured = '0', "configured off an abandoned SET_ADDRESS");
      ck(dev_addr = std_logic_vector'("0000000"), "an abandoned SET_ADDRESS still took effect");
      ck(addressed = '0', "addressed off an abandoned SET_ADDRESS");

      -- 3. SET_ADDRESS: the old address stays live until the status stage.
      step('1', REQ_SET_ADDRESS, "000000000" & ea, '0', '0', '0', '1', TOK_SETUP, "0000000");
      idle;
      ck(dev_addr = std_logic_vector'("0000000"), "address changed before the status stage");
      idle;
      ck(dev_addr = std_logic_vector'("0000000"), "address changed before the status stage");
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
      idle;
      ck(dev_addr  = ea,  "address did not take effect after the status stage");
      ck(addressed = '1', "device not addressed after a completed SET_ADDRESS");

      -- 4. Now SET_CONFIGURATION is meaningful.
      step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, ea);
      idle;
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, ea);
      idle;
      ck(configured = '1', "device did not configure when it should have");

      -- 5. Re-address: the configuration survives until the status stage and
      --    must be gone immediately after it.
      step('1', REQ_SET_ADDRESS, "000000000" & eb, '0', '0', '0', '1', TOK_SETUP, ea);
      idle;
      ck(dev_addr   = ea,  "address changed before the status stage");
      ck(configured = '1', "configuration dropped before the re-address completed");
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, ea);
      idle;
      ck(dev_addr   = eb,  "re-address did not take effect");
      ck(configured = '0', "device stayed configured across a re-address");

      -- 6. And it can be configured again at the new address.
      step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, eb);
      idle;
      step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, eb);
      idle;
      ck(configured = '1', "device could not be reconfigured after a re-address");
    end loop;

    -- PHASE 5 (RANDOM)
    if not DIRECTED_ONLY then
      reset_dut;
      for k in 0 to 29999 loop
        if (nxt mod 4) = 0 then sv3 := '1'; else sv3 := '0'; end if;
        if (nxt mod 3) = 0 then ds3 := '1'; else ds3 := '0'; end if;
        if (nxt mod 4) = 0 then sa3 := '1'; else sa3 := '0'; end if;
        if (nxt mod 200) = 0 then br3 := '1'; else br3 := '0'; end if;
        if (nxt mod 2) = 0 then tv3 := '1'; else tv3 := '0'; end if;
        if (nxt mod 3) = 0 then
          ta3 := std_logic_vector(to_unsigned(nxt mod 128, 7));
        else
          ta3 := dev_addr;
        end if;
        step(sv3, reqs(nxt mod 4),
             std_logic_vector(to_unsigned(nxt mod 128, 16)),
             ds3, sa3, br3, tv3,
             std_logic_vector(to_unsigned(nxt mod 4, 2)), ta3);
      end loop;
    end if;

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

    write(ln, string'("steps=") & integer'image(steps)
          & string'(" checks=") & integer'image(checks)
          & string'(" reach=") & integer'image(n_reach) & string'("/80")
          & string'(" addrs=") & integer'image(n_areach) & string'("/128")
          & string'(" errors=") & integer'image(errors));
    writeline(output, ln);
    write(ln, string'("[enum] setups=") & integer'image(x_setup)
          & string'(" addr_changes=") & integer'image(x_chg)
          & string'(" resets=") & integer'image(x_rst)
          & string'(" wrong_addr=") & integer'image(x_wrong)
          & string'(" dropped=") & integer'image(x_drop));
    writeline(output, ln);
    write(ln, string'("[the whole point] premature address switches = ")
          & integer'image(n_premature));
    writeline(output, ln);
    if n_reach /= 80 or n_areach /= 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
(state × request × ack × reset) reached80 / 8080 / 8080 / 80
addresses swept128 / 128128 / 128128 / 128
full enumeration lifecycles8 / 88 / 88 / 8
Steps316483164831648
Checks executed289963289963289900
SETUP packets754275427468
address changes106410641023
bus resets157157162
tokens for another address371937193636
requests displaced before completion149914991517
premature address switches000
ResultPASSPASSPASS

All 128 addresses matter, not just a representative one. Address 0 is legal — it means "return to the default address" — and address 127 is the maximum; both are the values a design with an off-by-one or a truncated field gets wrong, and both are swept with the full "old address stays live" check around them.

11. Mutation Testing

#MutationVerilogSysVerVHDL
C1the address is applied when the request arrives142863142863142109
C4the status stage never retires the request908229082290849
C6the address is taken from the wrong bits of wValue882378823788595
C2a bus reset does not return the device to address 0654216542165451
C3SET_CONFIGURATION is accepted in the DEFAULT state320323203231754
C7the device claims to be addressed when the request arrives307653076529794
C5re-addressing leaves the device configured105171051710114
—unmutated baseline000

All seven die in all three languages, and C1 — the defect this chapter exists for — scores highest of the seven.

Directed against random

#V allV directedV randomVHDL allVHDL directedVHDL random
C114286347151381481421094715137394
C26542116086381365451160863843
C332032144318883175414431610
C490822897899259084989789952
C5105174410473101144410070
C68823725638567488595256386032
C7307657630689297947629718

Every directed column is identical across Verilog and VHDL — 4715, 1608, 144, 897, 44, 2563, 76 — which localises the whole cross-language spread to the random half, where the generators differ by construction. The largest remaining spread is C7 at 3%.

12. Two Mutations Had a Directed Score of Zero

The first decomposition of this chapter read like this:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   MUT     V-DIR
   C3          0        SET_CONFIG accepted in DEFAULT
   C5          0        re-address leaves device configured
   C7          4        claims addressed too early

Zero is not a small number here, it is a different kind of number: it means the directed phases could not detect the defect at all, and the mutation was being killed entirely by luck in the random phase.

The cause was embarrassing and general. The exhaustive phase drove every request in every state — and never followed any of them with a status stage. A request that is never completed changes nothing, so a phase that never completes one tests nothing. It was 80 scenarios of carefully enumerated stimulus with no observable consequence.

The fix was two phases rather than a bigger sweep:

  • Phase 4b completes every request in every state — 5 × 4 scenarios, each followed by a real status stage and then a second one that must change nothing further. C3 went 0 → 88, C5 went 0 → 44.
  • Phase 4c runs the chapter's own question as stimulus: eight complete enumerations, each with a different address pair, from attach through refused-configuration, address assignment, configuration, re-address, and re-configuration — with an explicit check at every stage. C3 → 144, C5 → 44, and the address rule itself picked up 848 more kills.

C7 needed one more thing. It is only observable through addressed while a request is outstanding, and nothing checked that. Phase 4c gained an abandoned request — the host asks for an address, changes its mind, and asks for a configuration instead — with addressed checked throughout. C7 went 4 → 76.

13. The Self-Check Was Wrong Twice, and Both Were False Positives

The design carries its own n_early_switch counter, so that the central rule is a published number rather than a claim. Writing it took three attempts and the first two are more instructive than the third.

Attempt 1 — compare the live address to the pending one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   if ((pend_kind == P_ADDR) && (addr_r == pend_addr))
     early_c <= early_c + 1;

   9607 false positives.

   SET_ADDRESS(0) to a device already at address 0 is
   LEGAL -- it means "return to the default address" --
   and it makes addr_r == pend_addr with nothing wrong.

Attempt 2 — remember the address when the request arrived and check it has not moved.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   addr_at_req <= addr_r;                       // on SETUP
   ...
   if ((pend_kind == P_ADDR) && (addr_r != addr_at_req))
     early_c <= early_c + 1;

   29809 false positives -- WORSE.

   A new SET_ADDRESS arriving on the same edge as the
   PREVIOUS one's status stage captures the pre-change
   address, and the address then legitimately changes.

Attempt 3 — capture the address that will be live after this edge.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   addr_at_req <= (status_ack && (pend_kind == P_ADDR))
                  ? pend_addr    // the change happening NOW
                  : addr_r;

   0 false positives, and the property is exactly right:
   while a SET_ADDRESS is outstanding, the live address has
   not moved from what it was when the request arrived.

14. Follow-Ups the Interviewer Will Ask

"Why does the host read a descriptor at address 0 before assigning an address?" To learn bMaxPacketSize0. It cannot read a descriptor properly without knowing the control endpoint's packet size, so the first read is deliberately minimal and exists only to enable the second.

"What puts the device in the default state?" A port reset — an electrical event driven by the hub at the host's request. Not a message. This is why replugging fixes a wedged device: the reset happens before anything else.

"Is SET_ADDRESS(0) legal?" Yes, and it means "return to the default address." It is also the value that breaks any design that infers "addressed" from a non-zero address, which is mutation C3's territory.

"What happens if the device is re-addressed while configured?" It becomes unconfigured. The Address state is by definition not configured, and the host must configure it again. That is property 6 and mutation C5.

"How long does the device have to switch addresses?" There is a recovery interval after the status stage — 2 ms in USB 2.0 — during which the host will not address it. That window exists precisely because the switch is not instantaneous, and it is the second half of the same rule.

"What if two devices are attached at once?" The host resets and enumerates one port at a time, because both would answer at address 0 simultaneously. This is the only reason address 0 works at all, and it is why enumeration is serialised across a hub.

"Where does this go wrong in practice?" Almost always one of three places: the address applied too early (C1), the descriptor read at the wrong packet size, or a device that does not accept a SETUP while halted — which is chapter 27.1's property 6.

15. UVM: A Scoreboard for a Multi-Stage Transfer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A control transfer is THREE transactions that mean one thing, and almost
// every enumeration bug lives in the relationship between them rather than
// inside any one. So this scoreboard is built around the stages, and its
// central assertion is about a value that must NOT have changed yet.
typedef enum { STG_SETUP, STG_DATA, STG_STATUS } stage_e;

class ctrl_item extends uvm_sequence_item;
  `uvm_object_utils(ctrl_item)

  rand stage_e      stage;
  rand bit [7:0]    bRequest;
  rand bit [15:0]   wValue;
  rand bit [6:0]    sent_to_addr;   // the address the HOST used
  rand bit          bus_reset;

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

  constraint c_reset_is_rare { bus_reset dist { 0 := 199, 1 := 1 }; }
endclass


class enum_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(enum_scoreboard)

  uvm_analysis_imp #(ctrl_item, enum_scoreboard) ap;

  localparam bit [7:0] SET_ADDRESS = 8'h05, SET_CONFIG = 8'h09;

  // ---- what has actually COMPLETED ----
  bit [6:0] exp_addr;
  bit       exp_addressed, exp_configured;

  // ---- what is merely OUTSTANDING ----
  bit       pend_addr_valid, pend_conf_valid;
  bit [6:0] pend_addr;

  int unsigned n_addr_change, n_displaced, n_wrong_addr_used;
  int unsigned n_outstanding_cycles;    // evidence the window was observed

  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction

  function void write(ctrl_item t);
    bit pav, pcv;
    bit [6:0] pa;

    if (t.bus_reset) begin
      // A reset returns the device to DEFAULT from anywhere, and discards
      // anything outstanding. Nothing survives it.
      exp_addr = 7'd0; exp_addressed = 0; exp_configured = 0;
      pend_addr_valid = 0; pend_conf_valid = 0;
      return;
    end

    // ---- THE CENTRAL CHECK ----
    //
    // While a SET_ADDRESS is outstanding, the host is still talking to the
    // OLD address -- so a transaction the host sent to the old address must
    // be one the device answered. If the DUT has already moved, the host's
    // status stage is unreachable and enumeration cannot complete.
    if (pend_addr_valid) begin
      n_outstanding_cycles++;
      if (t.sent_to_addr != exp_addr)
        `uvm_error("USB/ENUM",
          $sformatf("host addressed 0x%02h while a SET_ADDRESS to 0x%02h was outstanding: it should still be using 0x%02h",
                    t.sent_to_addr, pend_addr, exp_addr))
    end else begin
      if (t.sent_to_addr != exp_addr) n_wrong_addr_used++;
    end

    // snapshot before this transaction is applied -- the status stage
    // resolves what was outstanding BEFORE it, never what arrives with it
    pav = pend_addr_valid;  pcv = pend_conf_valid;  pa = pend_addr;

    case (t.stage)
      STG_SETUP: begin
        if (pav || pcv) n_displaced++;   // legal: the host may abandon
        pend_addr_valid = 0; pend_conf_valid = 0;
        case (t.bRequest)
          SET_ADDRESS: begin
            pend_addr = t.wValue[6:0];
            pend_addr_valid = 1;
            // NOT applied. exp_addr is untouched, deliberately.
          end
          SET_CONFIG: begin
            // Gated on a COMPLETED SET_ADDRESS, not on a non-zero address:
            // SET_ADDRESS(0) is legal and means "return to default".
            pend_conf_valid = exp_addressed;
          end
          default: ; // GET_DESCRIPTOR and friends leave nothing pending
        endcase
      end

      STG_DATA: ;   // moves bytes; changes no state

      STG_STATUS: begin
        pend_addr_valid = 0; pend_conf_valid = 0;
        if (pav) begin
          exp_addr       = pa;       // NOW
          exp_addressed  = 1;
          exp_configured = 0;        // re-addressing unconfigures
          n_addr_change++;
        end else if (pcv) begin
          exp_configured = 1;
        end
      end
    endcase
  endfunction

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

    `uvm_info("USB/ENUM",
      $sformatf("%0d address changes | %0d displaced requests | %0d outstanding-window transactions",
                n_addr_change, n_displaced, n_outstanding_cycles), UVM_LOW)

    // The window in which the rule can be broken is SMALL, and a run that
    // never spent time in it has not tested the rule however long it ran.
    if (n_outstanding_cycles == 0)
      `uvm_error("USB/COV",
        "no transaction ever occurred while a SET_ADDRESS was outstanding: the central rule was never at risk")
    if (n_addr_change == 0)
      `uvm_error("USB/COV",
        "no SET_ADDRESS ever completed in this run")
    if (n_displaced == 0)
      `uvm_error("USB/COV",
        "no request was ever abandoned before completion: the displacement path was never exercised")
  endfunction
endclass

16. Common Misconceptions

"SET_ADDRESS takes effect when the device decodes it." After the status stage. This is the whole chapter.

"The device announces itself when plugged in." The hub notices electrically; the host finds out when it next polls the hub.

"Address 0 is invalid." It is the default address every device uses before assignment, and SET_ADDRESS(0) is a legal request meaning "go back to it".

"A device is addressed once the host sends the request." Not until the status stage completes. A device that thinks otherwise accepts a SET_CONFIGURATION it should refuse.

"Re-addressing keeps the configuration." It does not. The Address state is unconfigured by definition.

"Two devices can enumerate at once." Both would answer at address 0. The host enumerates one port at a time.

"The address change is instantaneous." There is a recovery interval — 2 ms in USB 2.0 — during which the host must not address the device.

"A bus reset is a message." It is an electrical condition on the port, which is why it works on a device too confused to parse anything.

"Exhaustive request coverage means the transfers are covered." Two mutations scored 0 directed against an 80/80 sweep, because the sweep never completed a single transfer.

17. Exercises

1. Trace exactly what the host observes if the device applies the address on the SETUP. At which step does enumeration fail, and what does the host's log say?

2. State the general principle behind the rule in one sentence that does not mention USB, then give two examples from other protocols.

3. The design keeps pend_kind separate from have_addr and decodes state from both. Construct the input sequence that drops a request in the single-register version, and say what its failure rate depends on.

4. Attempt 2 of the self-check produced more false positives than attempt 1. Explain why, and give the general lesson about approximating a property.

5. C3 and C5 had directed scores of exactly 0 against a genuinely exhaustive 80/80 sweep. Explain how both can be true, and propose a coverage metric that would have shown the gap.

6. Add the 2 ms recovery interval. Which of the seven properties change, and what new one is needed?

7. SET_ADDRESS(0) is legal. Enumerate everything in the design and the bench that would break if it were not handled, and say which of those breaks would be silent.

18. Summary

IdeaWhy it matters
SET_ADDRESS applies after the status stagethe acknowledgement must come from the old address
A device that switches early is invisiblethe host concludes nothing is attached
The general rulea transport-changing request is acked on the old transport
The first descriptor read is at address 0to learn bMaxPacketSize0 before anything else
Attach is detected electrically, reported when polleda hub is a device and devices do not initiate
SET_ADDRESS(0) is legalso never infer "addressed" from a non-zero address
A re-address unconfiguresthe Address state is unconfigured by definition
A bus reset returns to DEFAULT from anywherewhich is why replugging fixes things
State ≠ outstanding requestone register for both silently drops requests
Status stage decoded before the SETUPso a same-edge request survives
Shadow models need a snapshot, then decideblocking assignments do not model hardware
A checker needs a false-positive test first9607 then 29809 false alarms, on a correct design
Exhaustive over requests ≠ over transferstwo mutations scored 0 against an 80/80 sweep
80 states, 128 addresses, 7 mutations0 premature switches in 289,963 checks

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o en_v.out en_v.v en_v_tb.v && ./en_v.out
SystemVerilogiverilog -g2012 -o en_sv.out en_sv.sv en_sv_tb.sv && ./en_sv.out
VHDL-2008 analysenvc --std=2008 -a en_vhdl.vhd en_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_en_vhdl
VHDL-2008 runnvc --std=2008 -r tb_en_vhdl
One mutationiverilog -g2005 -DMUT_C1 -o mm en_v_mut.v en_v_tb.v && ./mm
Directed only (Verilog)iverilog -g2005 -DDIRECTED_ONLY -o mm en_v_mut.v en_v_tb.v && ./mm
Directed only (VHDL)nvc --std=2008 -e -gDIRECTED_ONLY=true tb_en_vhdl

All three implementations pass with 0 errors: all 80 combinations of state, request, status stage and bus reset; all 128 addresses swept with the "old address stays live" check around each; eight complete enumeration lifecycles; zero premature address switches in 289,963 checks; and every one of the seven mutations killed by directed stimulus alone, with all seven directed scores identical across languages.


Chapter 27.4 — The Endpoints Question is the next thing the interviewer reaches for, because enumeration ends with endpoints becoming usable and the obvious follow-up is what they actually are. The answer hinges on a detail the numbering hides: endpoint 1 IN and endpoint 1 OUT are two different endpoints, with separate buffers, separate toggles and separate halt states.

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.