Skip to content
VLSI Mentor

USB · Module 21

Controller FSMs

A bus reset arrives in any state and always returns to Default — and returning to Default is not enough, because every endpoint's toggle, halt, buffer and pointer holds session state that must be flushed with it.

Four chapters have built four blocks. 21.1 holds a toggle and a halt per endpoint. 21.2 holds buffered packets. 21.3 answers requests. 21.4 holds two pointers into a buffer.

This chapter is the state machine above all of them — and the reason it exists is that one signal has to reach down into every one.

1. The Six States

StateMeaningWhat works
ATTACHEDplugged in, no VBUSnothing
POWEREDVBUS present, not yet resetnothing — the device waits
DEFAULTa bus reset has happenedaddress 0, endpoint 0 only
ADDRESSSET_ADDRESS gave it a unique addressstill endpoint 0 only
CONFIGUREDSET_CONFIGURATION selected onenow the other endpoints exist
SUSPENDEDthe bus has been idle for over 3 msnothing, until resume

Enumeration walks the first five in order, and the walk is short: power up, get reset, get an address, get configured. A device that reaches CONFIGURED is a working device.

2. A Bus Reset Arrives in Any State

Here is the rule that shapes the block:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   A BUS RESET CAN ARRIVE IN ANY STATE,
   AND ALWAYS RETURNS TO DEFAULT.

Not "usually". Not "from the states where it makes sense". The host may reset the bus at any moment — because a driver was reloaded, because a user opened a laptop lid, because something further up the tree went wrong and the hub reset its whole downstream port. A CONFIGURED device happily moving data becomes a DEFAULT device at address 0, with no warning and no negotiation.

3. Returning to Default Is Not Enough

Now the part that gets missed, and the reason this chapter sits at the end of the module rather than the beginning.

The device's state is not just the six values above. It also includes, spread across the four blocks this module has built:

Held whereWhatFrom
every endpointthe DATA toggle21.1
every endpointthe HALT condition21.1
every endpointbuffered packets and their lengths21.2
every endpointthe committed and provisional pointers21.4

A bus reset must flush all of it.

So the design has one output, ep_flush, that goes to every endpoint and clears everything listed above.

4. SET_CONFIGURATION Flushes Too

Same reasoning, narrower scope. Selecting a configuration re-creates the endpoints: a different configuration may have a different set of them, with different packet sizes and different types. So their toggles restart at DATA0 and their halts are cleared.

A host that reconfigures a device without re-enumerating it depends on this, and the failure looks identical to the reset case — one packet lost at the start of the new configuration.

One signal, four blocks

The device FSM asserts ep_flush on a bus reset or a SET_CONFIGURATION. That one signal clears the data toggle and halt in the endpoint controller from chapter 21.1, the buffered packets in the ping-pong FIFO from chapter 21.2, and the committed and provisional pointers in the packet engine from chapter 21.4.Bus resetfrom ANY stateusb_device_fsmasserts ep_flushEndpoint ctrltoggle + halt · ch 21.1SET_CONFIGURATIONre-creates the endpointsPing-pong FIFObuffered packets · ch 21.2Packet engineboth pointers · ch 21.4ep_flush12
ep_flush is asserted by a bus reset and by SET_CONFIGURATION, and reaches every piece of per-endpoint state this module has built. Forgetting either source costs exactly one silently-dropped packet at the start of the next session.

5. Suspend Is Orthogonal, and Must Remember

The last structural decision. Suspend is not a seventh place in the sequence — it is something that happens to a state.

A configured device whose bus goes idle suspends. When the bus wakes up, the device is configured again. It does not re-enumerate; it does not lose its address; the host expects to carry on exactly where it left off.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   CONFIGURED --suspend--> SUSPENDED --resume--> CONFIGURED
   ADDRESS    --suspend--> SUSPENDED --resume--> ADDRESS
   DEFAULT    --suspend--> SUSPENDED --resume--> DEFAULT

So the machine has to save the state it suspended from and return to it. A resume that always lands in DEFAULT forces a full re-enumeration after every idle period — which a laptop produces constantly. The device works; it just takes a second to wake up, every single time, and the bug is reported as "slow" rather than as "broken". Mutation J2, at 143 000 failures.

The device state machine

A six-state device machine. ATTACHED moves to POWERED when VBUS arrives, POWERED to DEFAULT on a bus reset, DEFAULT to ADDRESS on SET_ADDRESS with a non-zero address, and ADDRESS to CONFIGURED on SET_CONFIGURATION with a non-zero configuration. SET_ADDRESS with zero returns ADDRESS to DEFAULT and SET_CONFIGURATION with zero returns CONFIGURED to ADDRESS. CONFIGURED suspends to SUSPENDED and resume returns it to CONFIGURED.ATTACHEDPOWEREDDEFAULTADDRESSCONFIGUREDSUSPENDEDVBUSVBUSbus resetbus resetSET_ADDRESS(n)SET_ADDRESS(n)SET_ADDRESS(0)SET_ADDRESS(0)SET_CONFIGURATION(n)SET_CONFIGURATION(n)SET_CONFIGURATION(0)SET_CONFIGURATION(0)bus idle 3 msbus idle 3 msresumeresume
A bus reset returns to DEFAULT from every state (those six edges are omitted from the drawing so the enumeration walk stays readable). Suspend and resume are drawn from CONFIGURED only; the same pair exists from DEFAULT and ADDRESS, and resume always returns to whichever state it left.

6. What We Are Building

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  usb_device_fsm

  inputs                        outputs
  ------                        -------
  vbus                          state        6 states
  bus_reset                     dev_address  [6:0]
  suspend_req                   dev_config   [3:0]
  resume_event                  ep_flush     -> EVERY endpoint
  set_address / address_val     endpoints_usable
  set_config  / config_val      suspended
                                saved_state  what resume returns to

  n_resets / n_flushes / n_suspends / n_resumes / n_configured

The priority order in the next-state logic is itself part of the specification:

PriorityConditionWhy it outranks what follows
1!vbusa device with no power has no state to preserve
2bus_resetthe host has stopped believing whatever the device thought it was
3in ATTACHEDVBUS just arrived: become POWERED
4in SUSPENDEDonly a resume (or 1 and 2 above) leaves
5suspend_reqan idle bus suspends from wherever we are
6SET_ADDRESSonly legal in DEFAULT or ADDRESS
7SET_CONFIGURATIONonly legal in ADDRESS or CONFIGURED

7. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_device_fsm -- the state machine above everything else in this module,
// and the reset rule that has to reach down through all of it.
//
// THE SIX STATES
//
//   ATTACHED    plugged in, no VBUS. Nothing is powered.
//   POWERED     VBUS present. The device is alive and waiting to be reset.
//   DEFAULT     a bus reset has happened. The device answers at ADDRESS 0
//               and only on endpoint 0.
//   ADDRESS     SET_ADDRESS gave it a unique address. Still only endpoint 0.
//   CONFIGURED  SET_CONFIGURATION selected a configuration. NOW the other
//               endpoints exist.
//   SUSPENDED   the bus went idle for more than 3 ms.
//
// THE RULE THAT SHAPES THE BLOCK
//
// A BUS RESET CAN ARRIVE IN ANY STATE, AND ALWAYS RETURNS TO DEFAULT.
//
// Not "usually". Not "from the states where it makes sense". The host may
// reset the bus at any moment -- because a driver was reloaded, because the
// user opened a lid, because something further up the tree went wrong -- and
// every device below it must return to DEFAULT with address 0.
//
// AND RETURNING TO DEFAULT IS NOT ENOUGH
//
// This is the part that gets missed. The device's state is not just the six
// values above. It also includes, in every endpoint:
//
//     * the DATA toggle          (chapter 21.1)
//     * the HALT condition       (chapter 21.1)
//     * whatever is buffered     (chapters 21.2 and 21.4)
//
// A bus reset must flush ALL of it. A device that resets its top-level state
// and leaves a toggle at DATA1 re-enumerates perfectly and then drops the
// first packet of the new session, because the host restarts at DATA0 and
// the device is expecting DATA1. One packet. Silently. Only after a reset
// that happened to land while the toggle was odd.
//
// SET_CONFIGURATION FLUSHES TOO
//
// Same reasoning, narrower scope: selecting a configuration re-creates the
// endpoints, so their toggles restart at DATA0 and their halts are cleared.
// A host that reconfigures a device without re-enumerating it depends on
// this, and the failure looks identical to the reset case.
//
// SUSPEND IS ORTHOGONAL, AND MUST REMEMBER
//
// Suspend is not a seventh place in the sequence -- it is something that
// happens TO a state. A configured device that suspends and resumes is
// configured again; it does not have to re-enumerate. So the machine saves
// the state it suspended FROM and returns to it:
//
//     CONFIGURED --suspend--> SUSPENDED --resume--> CONFIGURED
//     ADDRESS    --suspend--> SUSPENDED --resume--> ADDRESS
//
// A resume that always lands in DEFAULT forces a re-enumeration after every
// idle period, which a laptop does constantly. The device works; it just
// takes a second to wake up, every single time.
module usb_device_fsm (
  input  wire       clk,
  input  wire       rst_n,

  input  wire       vbus,          // bus power is present
  input  wire       bus_reset,     // SE0 for long enough: a bus reset
  input  wire       suspend_req,   // the bus has been idle > 3 ms
  input  wire       resume_event,  // host resume or remote wakeup
  input  wire       set_address,   // a SET_ADDRESS request completed
  input  wire [6:0] address_val,
  input  wire       set_config,    // a SET_CONFIGURATION request completed
  input  wire [3:0] config_val,

  output wire [2:0] state,
  output wire [6:0] dev_address,
  output wire [3:0] dev_config,
  output wire       ep_flush,      // flush EVERY endpoint's toggle and halt
  output wire       endpoints_usable,
  output wire       suspended,
  output wire [2:0] saved_state,   // what SUSPENDED will return to

  output reg [31:0] n_resets,
  output reg [31:0] n_flushes,
  output reg [31:0] n_suspends,
  output reg [31:0] n_resumes,
  output reg [31:0] n_configured
);
  localparam [2:0] S_ATTACHED   = 3'd0,
                   S_POWERED    = 3'd1,
                   S_DEFAULT    = 3'd2,
                   S_ADDRESS    = 3'd3,
                   S_CONFIGURED = 3'd4,
                   S_SUSPENDED  = 3'd5;

  reg [2:0] st_r;
  reg [2:0] saved_r;
  reg [6:0] addr_r;
  reg [3:0] cfg_r;

  assign state       = st_r;
  assign saved_state = saved_r;
  assign dev_address = addr_r;
  assign dev_config  = cfg_r;
  assign suspended   = (st_r == S_SUSPENDED);

  // Only a CONFIGURED device has endpoints beyond endpoint 0. In DEFAULT and
  // ADDRESS the device exists and answers, but only on the control endpoint.
  assign endpoints_usable = (st_r == S_CONFIGURED);

  // A SET_ADDRESS is only meaningful in DEFAULT or ADDRESS; a
  // SET_CONFIGURATION only in ADDRESS or CONFIGURED. Anywhere else the
  // request should not have reached this block, and acting on it would move
  // the machine somewhere the host does not believe it is.
  wire addr_ok = set_address && ((st_r == S_DEFAULT) || (st_r == S_ADDRESS));
  wire cfg_ok  = set_config  && ((st_r == S_ADDRESS) || (st_r == S_CONFIGURED));

  // ---- THE FLUSH. Everything below this block -- every endpoint's toggle,
  // ---- every halt, every buffered byte -- is reset by this one signal.
  //
  // A bus reset flushes because the whole session restarts. A
  // SET_CONFIGURATION flushes because the endpoints themselves are
  // re-created. Both are required, and forgetting either costs exactly one
  // silently-dropped packet at the start of the next session.
  assign ep_flush = vbus && (bus_reset || cfg_ok);

  // ---- The next state, as one priority chain ----
  //
  // Order matters and is the design: losing VBUS outranks everything,
  // because a device with no power has no state to preserve. A bus reset
  // outranks every request, because the host has stopped believing whatever
  // the device thought it was.
  reg [2:0] st_n;
  reg [2:0] saved_n;
  reg [6:0] addr_n;
  reg [3:0] cfg_n;

  always @* begin
    st_n    = st_r;
    saved_n = saved_r;
    addr_n  = addr_r;
    cfg_n   = cfg_r;

    if (!vbus) begin
      // No power: back to ATTACHED, and nothing is retained.
      st_n    = S_ATTACHED;
      saved_n = S_POWERED;
      addr_n  = 7'd0;
      cfg_n   = 4'd0;
    end else if (bus_reset) begin
      // FROM ANY STATE. Address 0, no configuration, and ep_flush above
      // clears every endpoint.
      st_n    = S_DEFAULT;
      saved_n = S_DEFAULT;
      addr_n  = 7'd0;
      cfg_n   = 4'd0;
    end else if (st_r == S_ATTACHED) begin
      // VBUS just arrived. Powered, but not yet reset: no address, no
      // configuration, and nothing may be transferred until a reset.
      st_n    = S_POWERED;
      saved_n = S_POWERED;
    end else if (st_r == S_SUSPENDED) begin
      // Resume returns to the state we suspended FROM, not to DEFAULT.
      if (resume_event) st_n = saved_r;
    end else if (suspend_req) begin
      // Remember where we came from. This is the whole of "suspend is
      // orthogonal".
      saved_n = st_r;
      st_n    = S_SUSPENDED;
    end else if (addr_ok) begin
      // SET_ADDRESS(0) is legal and means "go back to DEFAULT" -- the host
      // uses it to de-address a device without resetting the bus.
      addr_n = address_val;
      st_n   = (address_val != 7'd0) ? S_ADDRESS : S_DEFAULT;
    end else if (cfg_ok) begin
      // SET_CONFIGURATION(0) likewise un-configures without de-addressing.
      cfg_n = config_val;
      st_n  = (config_val != 4'd0) ? S_CONFIGURED : S_ADDRESS;
    end
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r         <= S_ATTACHED;
      saved_r      <= S_POWERED;
      addr_r       <= 7'd0;
      cfg_r        <= 4'd0;
      n_resets     <= 32'd0;
      n_flushes    <= 32'd0;
      n_suspends   <= 32'd0;
      n_resumes    <= 32'd0;
      n_configured <= 32'd0;
    end else begin
      st_r    <= st_n;
      saved_r <= saved_n;
      addr_r  <= addr_n;
      cfg_r   <= cfg_n;

      if (vbus && bus_reset)                    n_resets   <= n_resets + 32'd1;
      if (ep_flush)                             n_flushes  <= n_flushes + 32'd1;
      if ((st_n == S_SUSPENDED) && (st_r != S_SUSPENDED))
                                                n_suspends <= n_suspends + 32'd1;
      if ((st_r == S_SUSPENDED) && (st_n != S_SUSPENDED))
                                                n_resumes  <= n_resumes + 32'd1;
      if ((st_n == S_CONFIGURED) && (st_r != S_CONFIGURED))
                                             n_configured <= n_configured + 32'd1;
    end
  end
endmodule

SET_ADDRESS(0) and SET_CONFIGURATION(0) are legal requests, not errors. A host uses them to de-address or un-configure a device without resetting the bus, and each moves the machine backwards one step. A design that treats a zero argument as "no change" leaves the device claiming a state the host has just taken away from it — mutations J3 and J4.

8. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_device_fsm -- the state machine above everything else in this module,
// and the reset rule that has to reach down through all of it.
//
// THE SIX STATES
//
//   ATTACHED    plugged in, no VBUS. Nothing is powered.
//   POWERED     VBUS present. The device is alive and waiting to be reset.
//   DEFAULT     a bus reset has happened. The device answers at ADDRESS 0
//               and only on endpoint 0.
//   ADDRESS     SET_ADDRESS gave it a unique address. Still only endpoint 0.
//   CONFIGURED  SET_CONFIGURATION selected a configuration. NOW the other
//               endpoints exist.
//   SUSPENDED   the bus went idle for more than 3 ms.
//
// THE RULE THAT SHAPES THE BLOCK
//
// A BUS RESET CAN ARRIVE IN ANY STATE, AND ALWAYS RETURNS TO DEFAULT.
//
// Not "usually". Not "from the states where it makes sense". The host may
// reset the bus at any moment -- because a driver was reloaded, because the
// user opened a lid, because something further up the tree went wrong -- and
// every device below it must return to DEFAULT with address 0.
//
// AND RETURNING TO DEFAULT IS NOT ENOUGH
//
// This is the part that gets missed. The device's state is not just the six
// values above. It also includes, in every endpoint:
//
//     * the DATA toggle          (chapter 21.1)
//     * the HALT condition       (chapter 21.1)
//     * whatever is buffered     (chapters 21.2 and 21.4)
//
// A bus reset must flush ALL of it. A device that resets its top-level state
// and leaves a toggle at DATA1 re-enumerates perfectly and then drops the
// first packet of the new session, because the host restarts at DATA0 and
// the device is expecting DATA1. One packet. Silently. Only after a reset
// that happened to land while the toggle was odd.
//
// SET_CONFIGURATION FLUSHES TOO
//
// Same reasoning, narrower scope: selecting a configuration re-creates the
// endpoints, so their toggles restart at DATA0 and their halts are cleared.
// A host that reconfigures a device without re-enumerating it depends on
// this, and the failure looks identical to the reset case.
//
// SUSPEND IS ORTHOGONAL, AND MUST REMEMBER
//
// Suspend is not a seventh place in the sequence -- it is something that
// happens TO a state. A configured device that suspends and resumes is
// configured again; it does not have to re-enumerate. So the machine saves
// the state it suspended FROM and returns to it:
//
//     CONFIGURED --suspend--> SUSPENDED --resume--> CONFIGURED
//     ADDRESS    --suspend--> SUSPENDED --resume--> ADDRESS
//
// A resume that always lands in DEFAULT forces a re-enumeration after every
// idle period, which a laptop does constantly. The device works; it just
// takes a second to wake up, every single time.
package usb_devfsm_pkg;
  // The six device states of USB 2.0 section 9.1. They are an enumeration
  // rather than an encoding because SUSPENDED is not the seventh step of a
  // sequence -- it is something that happens TO one of the others -- and a
  // type keeps that visible at every use.
  typedef enum logic [2:0] {
    S_ATTACHED   = 3'd0,   // plugged in, no VBUS
    S_POWERED    = 3'd1,   // VBUS present, awaiting a reset
    S_DEFAULT    = 3'd2,   // reset done: address 0, endpoint 0 only
    S_ADDRESS    = 3'd3,   // addressed, endpoint 0 only
    S_CONFIGURED = 3'd4,   // configured: the other endpoints exist
    S_SUSPENDED  = 3'd5    // bus idle > 3 ms
  } dev_state_e;
endpackage

module usb_device_fsm
  import usb_devfsm_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,

  input  logic       vbus,          // bus power is present
  input  logic       bus_reset,     // SE0 for long enough: a bus reset
  input  logic       suspend_req,   // the bus has been idle > 3 ms
  input  logic       resume_event,  // host resume or remote wakeup
  input  logic       set_address,   // a SET_ADDRESS request completed
  input  logic [6:0] address_val,
  input  logic       set_config,    // a SET_CONFIGURATION request completed
  input  logic [3:0] config_val,

  output dev_state_e state,
  output logic [6:0] dev_address,
  output logic [3:0] dev_config,
  output logic       ep_flush,      // flush EVERY endpoint's toggle and halt
  output logic       endpoints_usable,
  output logic       suspended,
  output dev_state_e saved_state,   // what SUSPENDED will return to

  output logic [31:0] n_resets,
  output logic [31:0] n_flushes,
  output logic [31:0] n_suspends,
  output logic [31:0] n_resumes,
  output logic [31:0] n_configured
);

  dev_state_e st_r, saved_r;
  logic [6:0] addr_r;
  logic [3:0] cfg_r;

  assign state       = st_r;
  assign saved_state = saved_r;
  assign dev_address = addr_r;
  assign dev_config  = cfg_r;
  assign suspended   = (st_r == S_SUSPENDED);

  // Only a CONFIGURED device has endpoints beyond endpoint 0. In DEFAULT and
  // ADDRESS the device exists and answers, but only on the control endpoint.
  assign endpoints_usable = (st_r == S_CONFIGURED);

  // A SET_ADDRESS is only meaningful in DEFAULT or ADDRESS; a
  // SET_CONFIGURATION only in ADDRESS or CONFIGURED. Anywhere else the
  // request should not have reached this block, and acting on it would move
  // the machine somewhere the host does not believe it is.
  logic addr_ok, cfg_ok;
  assign addr_ok = set_address && ((st_r == S_DEFAULT) || (st_r == S_ADDRESS));
  assign cfg_ok  = set_config  && ((st_r == S_ADDRESS) || (st_r == S_CONFIGURED));

  // ---- THE FLUSH. Everything below this block -- every endpoint's toggle,
  // ---- every halt, every buffered byte -- is reset by this one signal.
  //
  // A bus reset flushes because the whole session restarts. A
  // SET_CONFIGURATION flushes because the endpoints themselves are
  // re-created. Both are required, and forgetting either costs exactly one
  // silently-dropped packet at the start of the next session.
  assign ep_flush = vbus && (bus_reset || cfg_ok);

  // ---- The next state, as one priority chain ----
  //
  // Order matters and is the design: losing VBUS outranks everything,
  // because a device with no power has no state to preserve. A bus reset
  // outranks every request, because the host has stopped believing whatever
  // the device thought it was.
  dev_state_e st_n, saved_n;
  logic [6:0] addr_n;
  logic [3:0] cfg_n;

  always_comb begin
    st_n    = st_r;
    saved_n = saved_r;
    addr_n  = addr_r;
    cfg_n   = cfg_r;

    if (!vbus) begin
      // No power: back to ATTACHED, and nothing is retained.
      st_n    = S_ATTACHED;
      saved_n = S_POWERED;
      addr_n  = 7'd0;
      cfg_n   = 4'd0;
    end else if (bus_reset) begin
      // FROM ANY STATE. Address 0, no configuration, and ep_flush above
      // clears every endpoint.
      st_n    = S_DEFAULT;
      saved_n = S_DEFAULT;
      addr_n  = 7'd0;
      cfg_n   = 4'd0;
    end else if (st_r == S_ATTACHED) begin
      // VBUS just arrived. Powered, but not yet reset: no address, no
      // configuration, and nothing may be transferred until a reset.
      st_n    = S_POWERED;
      saved_n = S_POWERED;
    end else if (st_r == S_SUSPENDED) begin
      // Resume returns to the state we suspended FROM, not to DEFAULT.
      if (resume_event) st_n = saved_r;
    end else if (suspend_req) begin
      // Remember where we came from. This is the whole of "suspend is
      // orthogonal".
      saved_n = st_r;
      st_n    = S_SUSPENDED;
    end else if (addr_ok) begin
      // SET_ADDRESS(0) is legal and means "go back to DEFAULT" -- the host
      // uses it to de-address a device without resetting the bus.
      // Written as if/else rather than a ternary: an enum-valued ternary
      // needs an explicit cast in Icarus, and the cast would hide which of
      // the two states is the zero case.
      addr_n = address_val;
      if (address_val != 7'd0) st_n = S_ADDRESS;
      else                     st_n = S_DEFAULT;
    end else if (cfg_ok) begin
      // SET_CONFIGURATION(0) likewise un-configures without de-addressing.
      cfg_n = config_val;
      if (config_val != 4'd0) st_n = S_CONFIGURED;
      else                    st_n = S_ADDRESS;
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r         <= S_ATTACHED;
      saved_r      <= S_POWERED;
      addr_r       <= 7'd0;
      cfg_r        <= 4'd0;
      n_resets     <= '0;
      n_flushes    <= '0;
      n_suspends   <= '0;
      n_resumes    <= '0;
      n_configured <= '0;
    end else begin
      st_r    <= st_n;
      saved_r <= saved_n;
      addr_r  <= addr_n;
      cfg_r   <= cfg_n;

      if (vbus && bus_reset)                    n_resets   <= n_resets + 1;
      if (ep_flush)                             n_flushes  <= n_flushes + 1;
      if ((st_n == S_SUSPENDED) && (st_r != S_SUSPENDED))
                                                n_suspends <= n_suspends + 1;
      if ((st_r == S_SUSPENDED) && (st_n != S_SUSPENDED))
                                                n_resumes  <= n_resumes + 1;
      if ((st_n == S_CONFIGURED) && (st_r != S_CONFIGURED))
                                             n_configured <= n_configured + 1;
    end
  end
endmodule

9. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb_device_fsm -- the state machine above everything else in this module,
-- and the reset rule that has to reach down through all of it.
--
-- THE SIX STATES
--
--   ATTACHED    plugged in, no VBUS. Nothing is powered.
--   POWERED     VBUS present. The device is alive and waiting to be reset.
--   DEFAULT     a bus reset has happened. The device answers at ADDRESS 0
--               and only on endpoint 0.
--   ADDRESS     SET_ADDRESS gave it a unique address. Still only endpoint 0.
--   CONFIGURED  SET_CONFIGURATION selected a configuration. NOW the other
--               endpoints exist.
--   SUSPENDED   the bus went idle for more than 3 ms.
--
-- THE RULE THAT SHAPES THE BLOCK
--
-- A BUS RESET CAN ARRIVE IN ANY STATE, AND ALWAYS RETURNS TO DEFAULT.
--
-- Not "usually", and not "from the states where it makes sense". The host
-- may reset the bus at any moment, and every device below it must return to
-- DEFAULT with address 0.
--
-- AND RETURNING TO DEFAULT IS NOT ENOUGH
--
-- The device's state is not just the six values above. It also includes, in
-- every endpoint:
--
--     * the DATA toggle          (chapter 21.1)
--     * the HALT condition       (chapter 21.1)
--     * whatever is buffered     (chapters 21.2 and 21.4)
--
-- A bus reset must flush ALL of it. A device that resets its top-level state
-- and leaves a toggle at DATA1 re-enumerates perfectly and then drops the
-- first packet of the new session, because the host restarts at DATA0 and
-- the device is expecting DATA1. One packet, silently, and only after a
-- reset that happened to land while the toggle was odd.
--
-- SET_CONFIGURATION FLUSHES TOO
--
-- Selecting a configuration re-creates the endpoints, so their toggles
-- restart at DATA0 and their halts are cleared. A host that reconfigures a
-- device without re-enumerating it depends on this.
--
-- SUSPEND IS ORTHOGONAL, AND MUST REMEMBER
--
-- Suspend is not a seventh place in the sequence -- it is something that
-- happens TO a state. A configured device that suspends and resumes is
-- configured again:
--
--     CONFIGURED --suspend--> SUSPENDED --resume--> CONFIGURED
--     ADDRESS    --suspend--> SUSPENDED --resume--> ADDRESS
--
-- A resume that always lands in DEFAULT forces a re-enumeration after every
-- idle period, which a laptop does constantly.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb_devfsm_pkg is
  -- An enumeration rather than an encoding because SUSPENDED is not the
  -- seventh step of a sequence -- it is something that happens TO one of the
  -- others -- and a type keeps that visible at every use.
  type dev_state_t is (
    S_ATTACHED,    -- plugged in, no VBUS
    S_POWERED,     -- VBUS present, awaiting a reset
    S_DEFAULT,     -- reset done: address 0, endpoint 0 only
    S_ADDRESS,     -- addressed, endpoint 0 only
    S_CONFIGURED,  -- configured: the other endpoints exist
    S_SUSPENDED    -- bus idle > 3 ms
  );

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

package body usb_devfsm_pkg is
  function st_code(s : dev_state_t) return std_logic_vector is
  begin
    return std_logic_vector(to_unsigned(dev_state_t'pos(s), 3));
  end function;
end package body;

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

entity usb_device_fsm is
  port (
    clk              : in  std_logic;
    rst_n            : in  std_logic;

    vbus             : in  std_logic;   -- bus power is present
    bus_reset        : in  std_logic;   -- SE0 for long enough
    suspend_req      : in  std_logic;   -- the bus has been idle > 3 ms
    resume_event     : in  std_logic;   -- host resume or remote wakeup
    set_address      : in  std_logic;   -- a SET_ADDRESS completed
    address_val      : in  std_logic_vector(6 downto 0);
    set_config       : in  std_logic;   -- a SET_CONFIGURATION completed
    config_val       : in  std_logic_vector(3 downto 0);

    state            : out std_logic_vector(2 downto 0);
    dev_address      : out std_logic_vector(6 downto 0);
    dev_config       : out std_logic_vector(3 downto 0);
    ep_flush         : out std_logic;   -- flush EVERY endpoint
    endpoints_usable : out std_logic;
    suspended        : out std_logic;
    saved_state      : out std_logic_vector(2 downto 0);

    n_resets         : out std_logic_vector(31 downto 0);
    n_flushes        : out std_logic_vector(31 downto 0);
    n_suspends       : out std_logic_vector(31 downto 0);
    n_resumes        : out std_logic_vector(31 downto 0);
    n_configured     : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of usb_device_fsm is
  signal st_r, saved_r : dev_state_t := S_ATTACHED;
  signal addr_r : unsigned(6 downto 0) := (others => '0');
  signal cfg_r  : unsigned(3 downto 0) := (others => '0');

  signal st_n, saved_n : dev_state_t;
  signal addr_n : unsigned(6 downto 0);
  signal cfg_n  : unsigned(3 downto 0);

  signal addr_ok, cfg_ok, flush_s : std_logic;

  signal rst_c, fl_c, sp_c, rs_c, cf_c : unsigned(31 downto 0)
       := (others => '0');
begin
  state       <= st_code(st_r);
  saved_state <= st_code(saved_r);
  dev_address <= std_logic_vector(addr_r);
  dev_config  <= std_logic_vector(cfg_r);
  suspended   <= '1' when st_r = S_SUSPENDED else '0';

  -- Only a CONFIGURED device has endpoints beyond endpoint 0. In DEFAULT and
  -- ADDRESS the device exists and answers, but only on the control endpoint.
  endpoints_usable <= '1' when st_r = S_CONFIGURED else '0';

  -- A SET_ADDRESS is only meaningful in DEFAULT or ADDRESS; a
  -- SET_CONFIGURATION only in ADDRESS or CONFIGURED. Anywhere else the
  -- request should not have reached this block.
  addr_ok <= '1' when (set_address = '1'
                       and (st_r = S_DEFAULT or st_r = S_ADDRESS)) else '0';
  cfg_ok  <= '1' when (set_config = '1'
                       and (st_r = S_ADDRESS or st_r = S_CONFIGURED)) else '0';

  -- ---- THE FLUSH. Everything below this block -- every endpoint's toggle,
  -- ---- every halt, every buffered byte -- is reset by this one signal.
  flush_s  <= '1' when (vbus = '1' and (bus_reset = '1' or cfg_ok = '1'))
              else '0';
  ep_flush <= flush_s;

  -- ---- The next state, as one priority chain ----
  --
  -- Order matters and is the design: losing VBUS outranks everything,
  -- because a device with no power has no state to preserve. A bus reset
  -- outranks every request, because the host has stopped believing whatever
  -- the device thought it was.
  nextstate : process (st_r, saved_r, addr_r, cfg_r, vbus, bus_reset,
                       suspend_req, resume_event, addr_ok, cfg_ok,
                       address_val, config_val)
  begin
    st_n    <= st_r;
    saved_n <= saved_r;
    addr_n  <= addr_r;
    cfg_n   <= cfg_r;

    if vbus = '0' then
      -- No power: back to ATTACHED, and nothing is retained.
      st_n    <= S_ATTACHED;
      saved_n <= S_POWERED;
      addr_n  <= (others => '0');
      cfg_n   <= (others => '0');
    elsif bus_reset = '1' then
      -- FROM ANY STATE. Address 0, no configuration, and ep_flush above
      -- clears every endpoint.
      st_n    <= S_DEFAULT;
      saved_n <= S_DEFAULT;
      addr_n  <= (others => '0');
      cfg_n   <= (others => '0');
    elsif st_r = S_ATTACHED then
      -- VBUS just arrived. Powered, but not yet reset.
      st_n    <= S_POWERED;
      saved_n <= S_POWERED;
    elsif st_r = S_SUSPENDED then
      -- Resume returns to the state we suspended FROM, not to DEFAULT.
      if resume_event = '1' then
        st_n <= saved_r;
      end if;
    elsif suspend_req = '1' then
      -- Remember where we came from. This is the whole of "suspend is
      -- orthogonal".
      saved_n <= st_r;
      st_n    <= S_SUSPENDED;
    elsif addr_ok = '1' then
      -- SET_ADDRESS(0) is legal and means "go back to DEFAULT".
      addr_n <= unsigned(address_val);
      if unsigned(address_val) /= 0 then
        st_n <= S_ADDRESS;
      else
        st_n <= S_DEFAULT;
      end if;
    elsif cfg_ok = '1' then
      -- SET_CONFIGURATION(0) likewise un-configures without de-addressing.
      cfg_n <= unsigned(config_val);
      if unsigned(config_val) /= 0 then
        st_n <= S_CONFIGURED;
      else
        st_n <= S_ADDRESS;
      end if;
    end if;
  end process;

  regs : process (clk, rst_n)
  begin
    if rst_n = '0' then
      st_r    <= S_ATTACHED;
      saved_r <= S_POWERED;
      addr_r  <= (others => '0');
      cfg_r   <= (others => '0');
      rst_c   <= (others => '0');
      fl_c    <= (others => '0');
      sp_c    <= (others => '0');
      rs_c    <= (others => '0');
      cf_c    <= (others => '0');
    elsif rising_edge(clk) then
      st_r    <= st_n;
      saved_r <= saved_n;
      addr_r  <= addr_n;
      cfg_r   <= cfg_n;

      if vbus = '1' and bus_reset = '1' then
        rst_c <= rst_c + 1;
      end if;
      if flush_s = '1' then
        fl_c <= fl_c + 1;
      end if;
      if st_n = S_SUSPENDED and st_r /= S_SUSPENDED then
        sp_c <= sp_c + 1;
      end if;
      if st_r = S_SUSPENDED and st_n /= S_SUSPENDED then
        rs_c <= rs_c + 1;
      end if;
      if st_n = S_CONFIGURED and st_r /= S_CONFIGURED then
        cf_c <= cf_c + 1;
      end if;
    end if;
  end process;

  n_resets     <= std_logic_vector(rst_c);
  n_flushes    <= std_logic_vector(fl_c);
  n_suspends   <= std_logic_vector(sp_c);
  n_resumes    <= std_logic_vector(rs_c);
  n_configured <= std_logic_vector(cf_c);
end architecture;

10. Seeing the Reset Reach Down

A bus reset from CONFIGURED, and the flush that has to go with it

usb_device_fsm — a bus reset from the working state

10 cycles
A ten-cycle waveform. The device walks from POWERED through DEFAULT and ADDRESS to CONFIGURED over the first four cycles, with ep_flush pulsing on the bus reset at cycle 1 and on the SET_CONFIGURATION at cycle 3. At cycle 5 a bus reset arrives while the device is CONFIGURED: ep_flush pulses again, the state returns to DEFAULT, the address and configuration go to zero and endpoints_usable falls.reset: flushreset: flushSET_CONFIG: flush tooSET_CONFIG: flush tooreset from CONFIGUREDreset from CONFIGUREDclkbus_resetset_addressset_configstatePOWEREDPOWEREDDEFAULTADDRESSCONFIGCONFIGDEFAULTDEFAULTDEFAULTDEFAULTdev_address0007770000dev_config0000110000ep_flushendpoints_usablet0t1t2t3t4t5t6t7t8t9
Cycle 5: a bus reset arrives while the device is happily CONFIGURED. The state returns to DEFAULT, the address and configuration are cleared, endpoints_usable drops — and ep_flush pulses, which is the part that reaches the toggles, the halts, the buffers and the pointers in the other four blocks.

ep_flush pulses three times in ten cycles, and every one of them matters. Cycle 1 and 5 are bus resets; cycle 3 is the SET_CONFIGURATION that re-creates the endpoints.

11. The Testbenches

Each suite sweeps every state against every combination of the eight control inputs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  6 states
    x  vbus  x  bus_reset  x  suspend_req  x  resume_event
    x  set_address  x  (address zero / non-zero)
    x  set_config   x  (config zero / non-zero)

    =  6 x 256  =  1536 one-step transitions

Reaching each starting state uses only legal transitions — goto_state walks the enumeration path rather than forcing registers — and each bench keeps a shadow model of all four pieces of retained state (the state, the saved state, the address and the configuration), compared both before and after every clock edge.

11.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_df_v;
  reg clk=0, rst_n=0;
  reg vbus=0, bus_reset=0, suspend_req=0, resume_event=0;
  reg set_address=0, set_config=0;
  reg [6:0] address_val=0;
  reg [3:0] config_val=0;
  wire [2:0] state, saved_state;
  wire [6:0] dev_address;
  wire [3:0] dev_config;
  wire ep_flush, endpoints_usable, suspended;
  wire [31:0] n_resets, n_flushes, n_suspends, n_resumes, n_configured;
  always #5 clk=~clk;

  usb_device_fsm dut (
    .clk(clk), .rst_n(rst_n), .vbus(vbus), .bus_reset(bus_reset),
    .suspend_req(suspend_req), .resume_event(resume_event),
    .set_address(set_address), .address_val(address_val),
    .set_config(set_config), .config_val(config_val), .state(state),
    .dev_address(dev_address), .dev_config(dev_config),
    .ep_flush(ep_flush), .endpoints_usable(endpoints_usable),
    .suspended(suspended), .saved_state(saved_state), .n_resets(n_resets),
    .n_flushes(n_flushes), .n_suspends(n_suspends), .n_resumes(n_resumes),
    .n_configured(n_configured));

  localparam [2:0] S_ATTACHED=0, S_POWERED=1, S_DEFAULT=2, S_ADDRESS=3,
                   S_CONFIGURED=4, S_SUSPENDED=5;

  // ---- SHADOW MODEL: independent copies of every piece of retained state.
  integer s_st, s_saved, s_addr, s_cfg;
  integer m_rst, m_flush, m_susp, m_res, m_cfgd;

  integer errors=0, i, a, b, c, d, e, f, g, h, st0;
  integer n_exh=0;
  integer n_vis [0:5];
  integer n_flush_from [0:5];
  integer n_resume_to [0:5];

  task check(input cond, input [639:0] msg);
    begin if (!cond) begin errors=errors+1;
      if (errors <= 25)
        $display("  FAIL: %0s (vbus=%b rst=%b susp=%b res=%b sa=%b a=%0d sc=%b c=%0d | st=%0d saved=%0d addr=%0d cfg=%0d flush=%b || model st=%0d sv=%0d, t=%0t)",
                 msg, vbus, bus_reset, suspend_req, resume_event, set_address,
                 address_val, set_config, config_val, state, saved_state,
                 dev_address, dev_config, ep_flush, s_st, s_saved, $time);
    end end
  endtask

  // The reference model is written as a nested case over the CURRENT state
  // where the design uses one flat priority chain -- a different route.
  task model(output integer e_st, output integer e_saved,
             output integer e_addr, output integer e_cfg, output e_flush);
    reg aok, cok;
    begin
      e_st = s_st; e_saved = s_saved; e_addr = s_addr; e_cfg = s_cfg;
      aok = set_address && ((s_st == S_DEFAULT) || (s_st == S_ADDRESS));
      cok = set_config  && ((s_st == S_ADDRESS) || (s_st == S_CONFIGURED));
      e_flush = vbus && (bus_reset || cok);

      if (!vbus) begin
        e_st = S_ATTACHED; e_saved = S_POWERED; e_addr = 0; e_cfg = 0;
      end else if (bus_reset) begin
        e_st = S_DEFAULT;  e_saved = S_DEFAULT; e_addr = 0; e_cfg = 0;
      end else begin
        case (s_st)
          S_ATTACHED:  begin e_st = S_POWERED; e_saved = S_POWERED; end
          S_SUSPENDED: if (resume_event) e_st = s_saved;
          default: begin
            if (suspend_req) begin
              e_saved = s_st; e_st = S_SUSPENDED;
            end else if (aok) begin
              e_addr = address_val;
              e_st = (address_val != 0) ? S_ADDRESS : S_DEFAULT;
            end else if (cok) begin
              e_cfg = config_val;
              e_st = (config_val != 0) ? S_CONFIGURED : S_ADDRESS;
            end
          end
        endcase
      end
    end
  endtask

  task check_comb;
    integer e_st, e_saved, e_addr, e_cfg;
    reg e_flush;
    begin
      model(e_st, e_saved, e_addr, e_cfg, e_flush);

      check(state === s_st[2:0],         "state matches the shadow model");
      check(saved_state === s_saved[2:0], "saved_state matches the model");
      check(dev_address === s_addr[6:0], "dev_address matches the model");
      check(dev_config === s_cfg[3:0],   "dev_config matches the model");
      check(ep_flush === e_flush,        "ep_flush matches the model");
      check(suspended === (s_st == S_SUSPENDED), "suspended matches the state");
      check(endpoints_usable === (s_st == S_CONFIGURED),
            "endpoints are usable only when CONFIGURED");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE property. A bus reset with power flushes every endpoint.
      //    Returning to DEFAULT without flushing leaves a stale toggle, and
      //    the first packet of the next session is silently dropped.
      if (vbus && bus_reset)
        check(ep_flush,
              "a bus reset did not flush the endpoints -- stale toggles survive into the new session");
      // 2. A SET_CONFIGURATION flushes too: the endpoints are re-created.
      if (vbus && set_config
          && ((state === S_ADDRESS) || (state === S_CONFIGURED)))
        check(ep_flush,
              "a SET_CONFIGURATION did not flush the endpoints it re-created");
      // 3. Endpoints beyond endpoint 0 exist only when CONFIGURED.
      if (endpoints_usable)
        check(state === S_CONFIGURED,
              "endpoints were usable outside the CONFIGURED state");
      // 4. A device with no configuration is never CONFIGURED.
      if (state === S_CONFIGURED)
        check(dev_config !== 4'd0,
              "the device is CONFIGURED with configuration 0");
      // 5. DEFAULT means address zero, by definition.
      if (state === S_DEFAULT)
        check(dev_address === 7'd0,
              "the device is in DEFAULT with a non-zero address");
      // 6. Nothing is retained without power.
      if (state === S_ATTACHED)
        check((dev_address === 7'd0) && (dev_config === 4'd0),
              "an unpowered device retained an address or a configuration");
      // 7. SUSPENDED always remembers somewhere real to go back to.
      if (state === S_SUSPENDED)
        check(saved_state !== S_SUSPENDED,
              "SUSPENDED remembers SUSPENDED -- resume would never leave");
      // 8. A flush never happens without power.
      if (!vbus) check(!ep_flush, "an unpowered device flushed its endpoints");

      if (s_st >= 0 && s_st <= 5) n_vis[s_st] = n_vis[s_st] + 1;
      if (e_flush && s_st >= 0 && s_st <= 5)
        n_flush_from[s_st] = n_flush_from[s_st] + 1;
      if ((s_st == S_SUSPENDED) && resume_event && vbus && !bus_reset
          && s_saved >= 0 && s_saved <= 5)
        n_resume_to[s_saved] = n_resume_to[s_saved] + 1;
    end
  endtask

  task model_step;
    integer e_st, e_saved, e_addr, e_cfg;
    reg e_flush;
    begin
      model(e_st, e_saved, e_addr, e_cfg, e_flush);
      if (vbus && bus_reset) m_rst = m_rst + 1;
      if (e_flush) m_flush = m_flush + 1;
      if ((e_st == S_SUSPENDED) && (s_st != S_SUSPENDED)) m_susp = m_susp + 1;
      if ((s_st == S_SUSPENDED) && (e_st != S_SUSPENDED)) m_res = m_res + 1;
      if ((e_st == S_CONFIGURED) && (s_st != S_CONFIGURED)) m_cfgd = m_cfgd + 1;
      s_st = e_st; s_saved = e_saved; s_addr = e_addr; s_cfg = e_cfg;
    end
  endtask

  task step;
    begin
      #1;
      check_comb;
      model_step;
      @(posedge clk); #1;
      check(state       === s_st[2:0],    "state tracked the model");
      check(saved_state === s_saved[2:0], "saved_state tracked the model");
      check(dev_address === s_addr[6:0],  "dev_address tracked the model");
      check(dev_config  === s_cfg[3:0],   "dev_config tracked the model");
      check(n_resets     === m_rst[31:0],   "n_resets matches the model");
      check(n_flushes    === m_flush[31:0], "n_flushes matches the model");
      check(n_suspends   === m_susp[31:0],  "n_suspends matches the model");
      check(n_resumes    === m_res[31:0],   "n_resumes matches the model");
      check(n_configured === m_cfgd[31:0],  "n_configured matches the model");
    end
  endtask

  task idle;
    begin
      bus_reset=0; suspend_req=0; resume_event=0;
      set_address=0; set_config=0;
    end
  endtask

  task hard_reset;
    begin
      rst_n=0; vbus=0; idle; address_val=0; config_val=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      s_st=S_ATTACHED; s_saved=S_POWERED; s_addr=0; s_cfg=0;
      m_rst=0; m_flush=0; m_susp=0; m_res=0; m_cfgd=0;
    end
  endtask

  // Walk the machine into a chosen state, using only legal transitions.
  task goto_state(input integer want);
    begin
      hard_reset;
      if (want == S_ATTACHED) begin
        vbus=0; idle; step;
      end else begin
        vbus=1; idle; step;                       // ATTACHED -> POWERED
        if (want != S_POWERED) begin
          bus_reset=1; step; idle;                // -> DEFAULT
          if (want == S_ADDRESS || want == S_CONFIGURED
              || want == S_SUSPENDED) begin
            set_address=1; address_val=7'd5; step; idle;   // -> ADDRESS
          end
          if (want == S_CONFIGURED || want == S_SUSPENDED) begin
            set_config=1; config_val=4'd1; step; idle;     // -> CONFIGURED
          end
          if (want == S_SUSPENDED) begin
            suspend_req=1; step; idle;                     // -> SUSPENDED
          end
        end
      end
      #1;
      check(state === want[2:0], "goto_state reached the requested state");
    end
  endtask

  initial begin
    for (i=0;i<6;i=i+1) begin
      n_vis[i]=0; n_flush_from[i]=0; n_resume_to[i]=0;
    end
    hard_reset;
    check(state === S_ATTACHED, "reset leaves the device ATTACHED");
    check(!endpoints_usable, "with no usable endpoints");

    // ===== A. EXHAUSTIVE one-step transition sweep =====
    // 6 states x vbus x bus_reset x suspend_req x resume_event
    //   x set_address x address non-zero x set_config x config non-zero
    // = 6 x 256 = 1536 transitions: every state against every combination
    // of the eight control inputs.
    for (st0=0; st0<6; st0=st0+1)
     for (a=0;a<2;a=a+1)
      for (b=0;b<2;b=b+1)
       for (c=0;c<2;c=c+1)
        for (d=0;d<2;d=d+1)
         for (e=0;e<2;e=e+1)
          for (f=0;f<2;f=f+1)
           for (g=0;g<2;g=g+1)
            for (h=0;h<2;h=h+1) begin
              goto_state(st0);
              vbus=a[0]; bus_reset=b[0]; suspend_req=c[0];
              resume_event=d[0]; set_address=e[0];
              address_val = f[0] ? 7'd9 : 7'd0;
              set_config=g[0];
              config_val  = h[0] ? 4'd1 : 4'd0;
              step;
              n_exh = n_exh + 1;
              idle;
            end
    $display("  exhaustive device-FSM sweep: %0d of %0d transitions verified",
             n_exh, 6*256);

    // ===== B. directed: the enumeration walk, and the reset that undoes it
    hard_reset;

    // 1. Plug in.
    vbus=1; step; idle;
    check(state === S_POWERED, "VBUS moves ATTACHED to POWERED");
    check(dev_address === 7'd0, "with no address");
    check(!endpoints_usable, "and no usable endpoints");

    // 2. The host resets the bus. This is the FIRST thing it does.
    bus_reset=1; step; idle;
    check(state === S_DEFAULT, "a bus reset moves POWERED to DEFAULT");
    check(n_flushes === 32'd1, "and flushes every endpoint");

    // 3. SET_ADDRESS.
    set_address=1; address_val=7'd7; step; idle;
    check(state === S_ADDRESS, "SET_ADDRESS(7) moves DEFAULT to ADDRESS");
    check(dev_address === 7'd7, "with the address applied");
    check(!endpoints_usable, "but still only endpoint 0");

    // 4. SET_CONFIGURATION.
    set_config=1; config_val=4'd1; #1;
    check(ep_flush,
          "SET_CONFIGURATION flushes: it re-creates the endpoints");
    step; idle;
    check(state === S_CONFIGURED, "and moves ADDRESS to CONFIGURED");
    check(endpoints_usable, "NOW the other endpoints exist");
    check(n_flushes === 32'd2, "that is the second flush");

    // 5. THE case. A bus reset from CONFIGURED -- the furthest state --
    //    returns all the way to DEFAULT and flushes everything.
    bus_reset=1; #1;
    check(ep_flush, "a bus reset from CONFIGURED flushes the endpoints");
    step; idle;
    check(state === S_DEFAULT, "and returns to DEFAULT from ANY state");
    check(dev_address === 7'd0, "the address is gone");
    check(dev_config === 4'd0, "the configuration is gone");
    check(!endpoints_usable, "and the endpoints are not usable");

    // 6. Suspend from CONFIGURED and resume BACK to CONFIGURED.
    goto_state(S_CONFIGURED);
    check(state === S_CONFIGURED, "configured again");
    suspend_req=1; step; idle;
    check(state === S_SUSPENDED, "an idle bus suspends the device");
    check(saved_state === S_CONFIGURED, "remembering where it came from");
    check(!endpoints_usable, "a suspended device transfers nothing");
    resume_event=1; step; idle;
    check(state === S_CONFIGURED,
          "and resume returns to CONFIGURED -- NOT to DEFAULT");
    check(endpoints_usable, "so no re-enumeration is needed");
    check(n_resumes === 32'd1, "one resume");

    // 7. Suspend from ADDRESS resumes to ADDRESS, not to CONFIGURED.
    goto_state(S_ADDRESS);
    suspend_req=1; step; idle;
    check(saved_state === S_ADDRESS, "suspended from ADDRESS");
    resume_event=1; step; idle;
    check(state === S_ADDRESS, "and resumed to ADDRESS");

    // 8. SET_ADDRESS(0) de-addresses without a bus reset.
    goto_state(S_ADDRESS);
    set_address=1; address_val=7'd0; step; idle;
    check(state === S_DEFAULT, "SET_ADDRESS(0) returns ADDRESS to DEFAULT");
    check(dev_address === 7'd0, "with address zero");

    // 9. SET_CONFIGURATION(0) un-configures without de-addressing.
    goto_state(S_CONFIGURED);
    set_config=1; config_val=4'd0; step; idle;
    check(state === S_ADDRESS,
          "SET_CONFIGURATION(0) returns CONFIGURED to ADDRESS");
    check(dev_address === 7'd5, "keeping the address");
    check(!endpoints_usable, "and losing the endpoints");

    // 10. Losing VBUS discards everything.
    goto_state(S_CONFIGURED);
    vbus=0; step; idle;
    check(state === S_ATTACHED, "losing VBUS returns to ATTACHED");
    check(dev_address === 7'd0, "the address is gone");
    check(dev_config === 4'd0, "and the configuration with it");
    check(!ep_flush, "and an unpowered device does not flush anything");

    // ===== C. randomised =====
    hard_reset; vbus=1;
    for (i=0;i<40000;i=i+1) begin
      vbus         = ({$random}%64)!=0;
      bus_reset    = ({$random}%24)==0;
      suspend_req  = ({$random}%12)==0;
      resume_event = ({$random}%4)==0;
      set_address  = ({$random}%6)==0;
      address_val  = {$random}%128;
      set_config   = ({$random}%6)==0;
      config_val   = {$random}%16;
      step;
    end

    for (i=0;i<6;i=i+1)
      check(n_vis[i] > 100, "every device state was visited many times");
    for (i=2;i<=4;i=i+1)
      check(n_flush_from[i] > 50,
            "endpoint flushes were issued from every configurable state");
    for (i=1;i<=4;i=i+1)
      check(n_resume_to[i] > 10,
            "resumes returned to every state that can be suspended from");

    $display("");
    $display("  REACH: transitions=%0d | visits: attached=%0d powered=%0d default=%0d address=%0d configured=%0d suspended=%0d",
             n_exh, n_vis[0], n_vis[1], n_vis[2], n_vis[3], n_vis[4], n_vis[5]);
    $display("  FLUSHES from: default=%0d address=%0d configured=%0d suspended=%0d | RESUMES to: powered=%0d default=%0d address=%0d configured=%0d",
             n_flush_from[2], n_flush_from[3], n_flush_from[4],
             n_flush_from[5], n_resume_to[1], n_resume_to[2],
             n_resume_to[3], n_resume_to[4]);
    $display("  COUNTERS: resets=%0d flushes=%0d suspends=%0d resumes=%0d configured=%0d",
             n_resets, n_flushes, n_suspends, n_resumes, n_configured);
    $display("  [Verilog] usb_device_fsm: %0d errors", errors);
    $display("  [Verilog] %0s", errors==0 ? "PASS" : "FAIL");
    $display("");
    $finish;
  end
endmodule

11.2 SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_df_sv;
  import usb_devfsm_pkg::*;

  logic clk=0, rst_n=0;
  logic vbus=0, bus_reset=0, suspend_req=0, resume_event=0;
  logic set_address=0, set_config=0;
  logic [6:0] address_val=0;
  logic [3:0] config_val=0;
  dev_state_e state, saved_state;
  logic [6:0] dev_address;
  logic [3:0] dev_config;
  logic ep_flush, endpoints_usable, suspended;
  logic [31:0] n_resets, n_flushes, n_suspends, n_resumes, n_configured;

  // Icarus seeds $random and $urandom identically, so an unseeded run would
  // replay the Verilog suite's stimulus exactly. See chapter 20.5 section 9.2.
  int urandom_seed = 21505;
  always #5 clk=~clk;

  usb_device_fsm dut (
    .clk, .rst_n, .vbus, .bus_reset, .suspend_req, .resume_event,
    .set_address, .address_val, .set_config, .config_val, .state,
    .dev_address, .dev_config, .ep_flush, .endpoints_usable, .suspended,
    .saved_state, .n_resets, .n_flushes, .n_suspends, .n_resumes,
    .n_configured);

  // ---- SHADOW MODEL: independent copies of every piece of retained state.
  int s_st, s_saved, s_addr, s_cfg;
  int m_rst, m_flush, m_susp, m_res, m_cfgd;

  int errors=0, i, a, b, c, d, e, f, g, h, st0;
  int n_exh=0;
  int n_vis [6];
  int n_flush_from [6];
  int n_resume_to [6];

  task automatic check(input bit cond, input string msg);
    // Icarus will not call .name() on a net, so the enum outputs are copied
    // into variables of the same type before being printed.
    dev_state_e st_v, sv_v;
    if (!cond) begin
      errors++;
      st_v = state; sv_v = saved_state;
      if (errors <= 25)
        $display("  FAIL: %0s (vbus=%b rst=%b susp=%b res=%b sa=%b a=%0d sc=%b c=%0d | st=%s saved=%s addr=%0d cfg=%0d flush=%b || model st=%0d sv=%0d, t=%0t)",
                 msg, vbus, bus_reset, suspend_req, resume_event, set_address,
                 address_val, set_config, config_val, st_v.name(),
                 sv_v.name(), dev_address, dev_config, ep_flush, s_st,
                 s_saved, $time);
    end
  endtask

  // The reference model is written as a nested case over the CURRENT state
  // where the design uses one flat priority chain -- a different route.
  task automatic model(output int e_st, output int e_saved,
                       output int e_addr, output int e_cfg,
                       output bit e_flush);
    bit aok, cok;
    begin
      e_st = s_st; e_saved = s_saved; e_addr = s_addr; e_cfg = s_cfg;
      aok = set_address && ((s_st == S_DEFAULT) || (s_st == S_ADDRESS));
      cok = set_config  && ((s_st == S_ADDRESS) || (s_st == S_CONFIGURED));
      e_flush = vbus && (bus_reset || cok);

      if (!vbus) begin
        e_st = S_ATTACHED; e_saved = S_POWERED; e_addr = 0; e_cfg = 0;
      end else if (bus_reset) begin
        e_st = S_DEFAULT;  e_saved = S_DEFAULT; e_addr = 0; e_cfg = 0;
      end else begin
        case (s_st)
          S_ATTACHED:  begin e_st = S_POWERED; e_saved = S_POWERED; end
          S_SUSPENDED: if (resume_event) e_st = s_saved;
          default: begin
            if (suspend_req) begin
              e_saved = s_st; e_st = S_SUSPENDED;
            end else if (aok) begin
              e_addr = address_val;
              e_st = (address_val != 0) ? S_ADDRESS : S_DEFAULT;
            end else if (cok) begin
              e_cfg = config_val;
              e_st = (config_val != 0) ? S_CONFIGURED : S_ADDRESS;
            end
          end
        endcase
      end
    end
  endtask

  task automatic check_comb;
    int e_st, e_saved, e_addr, e_cfg;
    bit e_flush;
    begin
      model(e_st, e_saved, e_addr, e_cfg, e_flush);

      check(state === dev_state_e'(s_st),         "state matches the shadow model");
      check(saved_state === dev_state_e'(s_saved), "saved_state matches the model");
      check(dev_address === 7'(s_addr), "dev_address matches the model");
      check(dev_config === 4'(s_cfg),   "dev_config matches the model");
      check(ep_flush === e_flush,        "ep_flush matches the model");
      check(suspended === (s_st == S_SUSPENDED), "suspended matches the state");
      check(endpoints_usable === (s_st == S_CONFIGURED),
            "endpoints are usable only when CONFIGURED");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE property. A bus reset with power flushes every endpoint.
      //    Returning to DEFAULT without flushing leaves a stale toggle, and
      //    the first packet of the next session is silently dropped.
      if (vbus && bus_reset)
        check(ep_flush,
              "a bus reset did not flush the endpoints -- stale toggles survive into the new session");
      // 2. A SET_CONFIGURATION flushes too: the endpoints are re-created.
      if (vbus && set_config
          && ((state === S_ADDRESS) || (state === S_CONFIGURED)))
        check(ep_flush,
              "a SET_CONFIGURATION did not flush the endpoints it re-created");
      // 3. Endpoints beyond endpoint 0 exist only when CONFIGURED.
      if (endpoints_usable)
        check(state === S_CONFIGURED,
              "endpoints were usable outside the CONFIGURED state");
      // 4. A device with no configuration is never CONFIGURED.
      if (state === S_CONFIGURED)
        check(dev_config !== 4'd0,
              "the device is CONFIGURED with configuration 0");
      // 5. DEFAULT means address zero, by definition.
      if (state === S_DEFAULT)
        check(dev_address === 7'd0,
              "the device is in DEFAULT with a non-zero address");
      // 6. Nothing is retained without power.
      if (state === S_ATTACHED)
        check((dev_address === 7'd0) && (dev_config === 4'd0),
              "an unpowered device retained an address or a configuration");
      // 7. SUSPENDED always remembers somewhere real to go back to.
      if (state === S_SUSPENDED)
        check(saved_state !== S_SUSPENDED,
              "SUSPENDED remembers SUSPENDED -- resume would never leave");
      // 8. A flush never happens without power.
      if (!vbus) check(!ep_flush, "an unpowered device flushed its endpoints");

      n_vis[s_st] = n_vis[s_st] + 1;
      if (e_flush) n_flush_from[s_st] = n_flush_from[s_st] + 1;
      if ((s_st == S_SUSPENDED) && resume_event && vbus && !bus_reset)
        n_resume_to[s_saved] = n_resume_to[s_saved] + 1;
    end
  endtask

  task automatic model_step;
    int e_st, e_saved, e_addr, e_cfg;
    bit e_flush;
    begin
      model(e_st, e_saved, e_addr, e_cfg, e_flush);
      if (vbus && bus_reset) m_rst = m_rst + 1;
      if (e_flush) m_flush = m_flush + 1;
      if ((e_st == S_SUSPENDED) && (s_st != S_SUSPENDED)) m_susp = m_susp + 1;
      if ((s_st == S_SUSPENDED) && (e_st != S_SUSPENDED)) m_res = m_res + 1;
      if ((e_st == S_CONFIGURED) && (s_st != S_CONFIGURED)) m_cfgd = m_cfgd + 1;
      s_st = e_st; s_saved = e_saved; s_addr = e_addr; s_cfg = e_cfg;
    end
  endtask

  task automatic step;
    begin
      #1;
      check_comb;
      model_step;
      @(posedge clk); #1;
      check(state       === dev_state_e'(s_st),    "state tracked the model");
      check(saved_state === dev_state_e'(s_saved), "saved_state tracked the model");
      check(dev_address === 7'(s_addr),  "dev_address tracked the model");
      check(dev_config  === 4'(s_cfg),   "dev_config tracked the model");
      check(n_resets     === 32'(m_rst),   "n_resets matches the model");
      check(n_flushes    === 32'(m_flush), "n_flushes matches the model");
      check(n_suspends   === 32'(m_susp),  "n_suspends matches the model");
      check(n_resumes    === 32'(m_res),   "n_resumes matches the model");
      check(n_configured === 32'(m_cfgd),  "n_configured matches the model");
    end
  endtask

  task automatic idle;
    begin
      bus_reset=0; suspend_req=0; resume_event=0;
      set_address=0; set_config=0;
    end
  endtask

  task automatic hard_reset;
    begin
      rst_n=0; vbus=0; idle; address_val=0; config_val=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      s_st=S_ATTACHED; s_saved=S_POWERED; s_addr=0; s_cfg=0;
      m_rst=0; m_flush=0; m_susp=0; m_res=0; m_cfgd=0;
    end
  endtask

  // Walk the machine into a chosen state, using only legal transitions.
  task automatic goto_state(input int want);
    begin
      hard_reset;
      if (want == S_ATTACHED) begin
        vbus=0; idle; step;
      end else begin
        vbus=1; idle; step;                       // ATTACHED -> POWERED
        if (want != S_POWERED) begin
          bus_reset=1; step; idle;                // -> DEFAULT
          if (want == S_ADDRESS || want == S_CONFIGURED
              || want == S_SUSPENDED) begin
            set_address=1; address_val=7'd5; step; idle;   // -> ADDRESS
          end
          if (want == S_CONFIGURED || want == S_SUSPENDED) begin
            set_config=1; config_val=4'd1; step; idle;     // -> CONFIGURED
          end
          if (want == S_SUSPENDED) begin
            suspend_req=1; step; idle;                     // -> SUSPENDED
          end
        end
      end
      #1;
      check(state === dev_state_e'(want), "goto_state reached the requested state");
    end
  endtask

  initial begin
    void'($urandom(urandom_seed));
    for (i=0;i<6;i++) begin
      n_vis[i]=0; n_flush_from[i]=0; n_resume_to[i]=0;
    end
    hard_reset;
    check(state === S_ATTACHED, "reset leaves the device ATTACHED");
    check(!endpoints_usable, "with no usable endpoints");

    // ===== A. EXHAUSTIVE one-step transition sweep =====
    // 6 states x vbus x bus_reset x suspend_req x resume_event
    //   x set_address x address non-zero x set_config x config non-zero
    // = 6 x 256 = 1536 transitions: every state against every combination
    // of the eight control inputs.
    for (st0=0; st0<6; st0=st0+1)
     for (a=0;a<2;a=a+1)
      for (b=0;b<2;b=b+1)
       for (c=0;c<2;c=c+1)
        for (d=0;d<2;d=d+1)
         for (e=0;e<2;e=e+1)
          for (f=0;f<2;f=f+1)
           for (g=0;g<2;g=g+1)
            for (h=0;h<2;h=h+1) begin
              goto_state(st0);
              vbus=a[0]; bus_reset=b[0]; suspend_req=c[0];
              resume_event=d[0]; set_address=e[0];
              address_val = f[0] ? 7'd9 : 7'd0;
              set_config=g[0];
              config_val  = h[0] ? 4'd1 : 4'd0;
              step;
              n_exh = n_exh + 1;
              idle;
            end
    $display("  exhaustive device-FSM sweep: %0d of %0d transitions verified",
             n_exh, 6*256);

    // ===== B. directed: the enumeration walk, and the reset that undoes it
    hard_reset;

    // 1. Plug in.
    vbus=1; step; idle;
    check(state === S_POWERED, "VBUS moves ATTACHED to POWERED");
    check(dev_address === 7'd0, "with no address");
    check(!endpoints_usable, "and no usable endpoints");

    // 2. The host resets the bus. This is the FIRST thing it does.
    bus_reset=1; step; idle;
    check(state === S_DEFAULT, "a bus reset moves POWERED to DEFAULT");
    check(n_flushes === 32'd1, "and flushes every endpoint");

    // 3. SET_ADDRESS.
    set_address=1; address_val=7'd7; step; idle;
    check(state === S_ADDRESS, "SET_ADDRESS(7) moves DEFAULT to ADDRESS");
    check(dev_address === 7'd7, "with the address applied");
    check(!endpoints_usable, "but still only endpoint 0");

    // 4. SET_CONFIGURATION.
    set_config=1; config_val=4'd1; #1;
    check(ep_flush,
          "SET_CONFIGURATION flushes: it re-creates the endpoints");
    step; idle;
    check(state === S_CONFIGURED, "and moves ADDRESS to CONFIGURED");
    check(endpoints_usable, "NOW the other endpoints exist");
    check(n_flushes === 32'd2, "that is the second flush");

    // 5. THE case. A bus reset from CONFIGURED -- the furthest state --
    //    returns all the way to DEFAULT and flushes everything.
    bus_reset=1; #1;
    check(ep_flush, "a bus reset from CONFIGURED flushes the endpoints");
    step; idle;
    check(state === S_DEFAULT, "and returns to DEFAULT from ANY state");
    check(dev_address === 7'd0, "the address is gone");
    check(dev_config === 4'd0, "the configuration is gone");
    check(!endpoints_usable, "and the endpoints are not usable");

    // 6. Suspend from CONFIGURED and resume BACK to CONFIGURED.
    goto_state(S_CONFIGURED);
    check(state === S_CONFIGURED, "configured again");
    suspend_req=1; step; idle;
    check(state === S_SUSPENDED, "an idle bus suspends the device");
    check(saved_state === S_CONFIGURED, "remembering where it came from");
    check(!endpoints_usable, "a suspended device transfers nothing");
    resume_event=1; step; idle;
    check(state === S_CONFIGURED,
          "and resume returns to CONFIGURED -- NOT to DEFAULT");
    check(endpoints_usable, "so no re-enumeration is needed");
    check(n_resumes === 32'd1, "one resume");

    // 7. Suspend from ADDRESS resumes to ADDRESS, not to CONFIGURED.
    goto_state(S_ADDRESS);
    suspend_req=1; step; idle;
    check(saved_state === S_ADDRESS, "suspended from ADDRESS");
    resume_event=1; step; idle;
    check(state === S_ADDRESS, "and resumed to ADDRESS");

    // 8. SET_ADDRESS(0) de-addresses without a bus reset.
    goto_state(S_ADDRESS);
    set_address=1; address_val=7'd0; step; idle;
    check(state === S_DEFAULT, "SET_ADDRESS(0) returns ADDRESS to DEFAULT");
    check(dev_address === 7'd0, "with address zero");

    // 9. SET_CONFIGURATION(0) un-configures without de-addressing.
    goto_state(S_CONFIGURED);
    set_config=1; config_val=4'd0; step; idle;
    check(state === S_ADDRESS,
          "SET_CONFIGURATION(0) returns CONFIGURED to ADDRESS");
    check(dev_address === 7'd5, "keeping the address");
    check(!endpoints_usable, "and losing the endpoints");

    // 10. Losing VBUS discards everything.
    goto_state(S_CONFIGURED);
    vbus=0; step; idle;
    check(state === S_ATTACHED, "losing VBUS returns to ATTACHED");
    check(dev_address === 7'd0, "the address is gone");
    check(dev_config === 4'd0, "and the configuration with it");
    check(!ep_flush, "and an unpowered device does not flush anything");

    // ===== C. randomised =====
    hard_reset; vbus=1;
    for (i=0;i<40000;i=i+1) begin
      vbus         = ($urandom%64)!=0;
      bus_reset    = ($urandom%24)==0;
      suspend_req  = ($urandom%12)==0;
      resume_event = ($urandom%4)==0;
      set_address  = ($urandom%6)==0;
      address_val  = $urandom%128;
      set_config   = ($urandom%6)==0;
      config_val   = $urandom%16;
      step;
    end

    for (i=0;i<6;i=i+1)
      check(n_vis[i] > 100, "every device state was visited many times");
    for (i=2;i<=4;i=i+1)
      check(n_flush_from[i] > 50,
            "endpoint flushes were issued from every configurable state");
    for (i=1;i<=4;i=i+1)
      check(n_resume_to[i] > 10,
            "resumes returned to every state that can be suspended from");

    $display("");
    $display("  REACH: transitions=%0d | visits: attached=%0d powered=%0d default=%0d address=%0d configured=%0d suspended=%0d",
             n_exh, n_vis[0], n_vis[1], n_vis[2], n_vis[3], n_vis[4], n_vis[5]);
    $display("  FLUSHES from: default=%0d address=%0d configured=%0d suspended=%0d | RESUMES to: powered=%0d default=%0d address=%0d configured=%0d",
             n_flush_from[2], n_flush_from[3], n_flush_from[4],
             n_flush_from[5], n_resume_to[1], n_resume_to[2],
             n_resume_to[3], n_resume_to[4]);
    $display("  COUNTERS: resets=%0d flushes=%0d suspends=%0d resumes=%0d configured=%0d",
             n_resets, n_flushes, n_suspends, n_resumes, n_configured);
    $display("  [SystemVerilog] usb_device_fsm: %0d errors", errors);
    $display("  [SystemVerilog] %0s", errors==0 ? "PASS" : "FAIL");
    $display("");
    $finish;
  end
endmodule

11.3 VHDL testbench

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

entity tb_df_vhdl is
end entity;

architecture sim of tb_df_vhdl is
  signal clk   : std_logic := '0';
  signal rst_n : std_logic := '0';
  signal vbus, bus_reset, suspend_req, resume_event : std_logic := '0';
  signal set_address, set_config : std_logic := '0';
  signal address_val : std_logic_vector(6 downto 0) := (others => '0');
  signal config_val  : std_logic_vector(3 downto 0) := (others => '0');

  signal state, saved_state : std_logic_vector(2 downto 0);
  signal dev_address : std_logic_vector(6 downto 0);
  signal dev_config  : std_logic_vector(3 downto 0);
  signal ep_flush, endpoints_usable, suspended : std_logic;
  signal n_resets, n_flushes, n_suspends, n_resumes, n_configured
       : std_logic_vector(31 downto 0);

  signal running : boolean := true;

  type cnt6_t is array (0 to 5) of integer;
begin
  clk <= not clk after 5 ns when running else '0';

  dut : entity work.usb_device_fsm
    port map (clk => clk, rst_n => rst_n, vbus => vbus,
              bus_reset => bus_reset, suspend_req => suspend_req,
              resume_event => resume_event, set_address => set_address,
              address_val => address_val, set_config => set_config,
              config_val => config_val, state => state,
              dev_address => dev_address, dev_config => dev_config,
              ep_flush => ep_flush, endpoints_usable => endpoints_usable,
              suspended => suspended, saved_state => saved_state,
              n_resets => n_resets, n_flushes => n_flushes,
              n_suspends => n_suspends, n_resumes => n_resumes,
              n_configured => n_configured);

  stim : process
    variable seed1 : positive := 9137;
    variable seed2 : positive := 4421;
    variable r1    : real;

    -- VHDL-2008 requires a shared variable to have a protected type, so the
    -- bookkeeping lives inside the single stimulus process instead.
    variable errors : integer := 0;

    -- SHADOW MODEL: independent copies of every piece of retained state.
    variable s_st, s_saved : dev_state_t := S_ATTACHED;
    variable s_addr, s_cfg : integer := 0;
    variable m_rst, m_flush, m_susp, m_res, m_cfgd : integer := 0;

    variable n_exh : integer := 0;
    variable n_vis, n_flush_from, n_resume_to : cnt6_t := (others => 0);

    procedure check(cond : boolean; msg : string) is
    begin
      if not cond then
        errors := errors + 1;
        if errors <= 25 then
          report "  FAIL: " & msg
               & " (vbus=" & std_logic'image(vbus)(2)
               & " rst=" & std_logic'image(bus_reset)(2)
               & " susp=" & std_logic'image(suspend_req)(2)
               & " res=" & std_logic'image(resume_event)(2)
               & " sa=" & std_logic'image(set_address)(2)
               & " a=" & integer'image(to_integer(unsigned(address_val)))
               & " sc=" & std_logic'image(set_config)(2)
               & " c=" & integer'image(to_integer(unsigned(config_val)))
               & " | st=" & integer'image(to_integer(unsigned(state)))
               & " saved=" & integer'image(to_integer(unsigned(saved_state)))
               & " addr=" & integer'image(to_integer(unsigned(dev_address)))
               & " cfg=" & integer'image(to_integer(unsigned(dev_config)))
               & " flush=" & std_logic'image(ep_flush)(2)
               & " || model st=" & integer'image(dev_state_t'pos(s_st))
               & " sv=" & integer'image(dev_state_t'pos(s_saved))
               & ")" severity note;
        end if;
      end if;
    end procedure;

    procedure rnd(variable v : out integer; m : integer) is
    begin
      uniform(seed1, seed2, r1);
      v := integer(floor(r1 * real(m)));
    end procedure;

    -- The reference model is written as a nested case over the CURRENT state
    -- where the design uses one flat priority chain -- a different route.
    procedure model(variable e_st, e_saved : out dev_state_t;
                    variable e_addr, e_cfg : out integer;
                    variable e_flush : out boolean) is
      variable aok, cok : boolean;
      variable av, cv   : integer;
    begin
      e_st := s_st; e_saved := s_saved; e_addr := s_addr; e_cfg := s_cfg;
      av := to_integer(unsigned(address_val));
      cv := to_integer(unsigned(config_val));
      aok := set_address = '1' and (s_st = S_DEFAULT or s_st = S_ADDRESS);
      cok := set_config = '1'  and (s_st = S_ADDRESS or s_st = S_CONFIGURED);
      e_flush := vbus = '1' and (bus_reset = '1' or cok);

      if vbus = '0' then
        e_st := S_ATTACHED; e_saved := S_POWERED; e_addr := 0; e_cfg := 0;
      elsif bus_reset = '1' then
        e_st := S_DEFAULT;  e_saved := S_DEFAULT; e_addr := 0; e_cfg := 0;
      else
        case s_st is
          when S_ATTACHED =>
            e_st := S_POWERED; e_saved := S_POWERED;
          when S_SUSPENDED =>
            if resume_event = '1' then e_st := s_saved; end if;
          when others =>
            if suspend_req = '1' then
              e_saved := s_st; e_st := S_SUSPENDED;
            elsif aok then
              e_addr := av;
              if av /= 0 then e_st := S_ADDRESS; else e_st := S_DEFAULT; end if;
            elsif cok then
              e_cfg := cv;
              if cv /= 0 then e_st := S_CONFIGURED; else e_st := S_ADDRESS; end if;
            end if;
        end case;
      end if;
    end procedure;

    procedure check_comb is
      variable e_st, e_saved : dev_state_t;
      variable e_addr, e_cfg : integer;
      variable e_flush : boolean;
    begin
      model(e_st, e_saved, e_addr, e_cfg, e_flush);

      check(state = st_code(s_st),             "state matches the shadow model");
      check(saved_state = st_code(s_saved),    "saved_state matches the model");
      check(to_integer(unsigned(dev_address)) = s_addr,
            "dev_address matches the model");
      check(to_integer(unsigned(dev_config)) = s_cfg,
            "dev_config matches the model");
      check((ep_flush = '1') = e_flush,        "ep_flush matches the model");
      check((suspended = '1') = (s_st = S_SUSPENDED),
            "suspended matches the state");
      check((endpoints_usable = '1') = (s_st = S_CONFIGURED),
            "endpoints are usable only when CONFIGURED");

      -- ---- SAFETY PROPERTIES, independent of the model ----
      -- 1. THE property. A bus reset with power flushes every endpoint.
      if vbus = '1' and bus_reset = '1' then
        check(ep_flush = '1',
              "a bus reset did not flush the endpoints -- stale toggles survive into the new session");
      end if;
      -- 2. A SET_CONFIGURATION flushes too: the endpoints are re-created.
      if vbus = '1' and set_config = '1'
         and (state = st_code(S_ADDRESS) or state = st_code(S_CONFIGURED)) then
        check(ep_flush = '1',
              "a SET_CONFIGURATION did not flush the endpoints it re-created");
      end if;
      -- 3. Endpoints beyond endpoint 0 exist only when CONFIGURED.
      if endpoints_usable = '1' then
        check(state = st_code(S_CONFIGURED),
              "endpoints were usable outside the CONFIGURED state");
      end if;
      -- 4. A device with no configuration is never CONFIGURED.
      if state = st_code(S_CONFIGURED) then
        check(to_integer(unsigned(dev_config)) /= 0,
              "the device is CONFIGURED with configuration 0");
      end if;
      -- 5. DEFAULT means address zero, by definition.
      if state = st_code(S_DEFAULT) then
        check(to_integer(unsigned(dev_address)) = 0,
              "the device is in DEFAULT with a non-zero address");
      end if;
      -- 6. Nothing is retained without power.
      if state = st_code(S_ATTACHED) then
        check(to_integer(unsigned(dev_address)) = 0
              and to_integer(unsigned(dev_config)) = 0,
              "an unpowered device retained an address or a configuration");
      end if;
      -- 7. SUSPENDED always remembers somewhere real to go back to.
      if state = st_code(S_SUSPENDED) then
        check(saved_state /= st_code(S_SUSPENDED),
              "SUSPENDED remembers SUSPENDED -- resume would never leave");
      end if;
      -- 8. A flush never happens without power.
      if vbus = '0' then
        check(ep_flush = '0', "an unpowered device flushed its endpoints");
      end if;

      n_vis(dev_state_t'pos(s_st)) := n_vis(dev_state_t'pos(s_st)) + 1;
      if e_flush then
        n_flush_from(dev_state_t'pos(s_st))
          := n_flush_from(dev_state_t'pos(s_st)) + 1;
      end if;
      if s_st = S_SUSPENDED and resume_event = '1' and vbus = '1'
         and bus_reset = '0' then
        n_resume_to(dev_state_t'pos(s_saved))
          := n_resume_to(dev_state_t'pos(s_saved)) + 1;
      end if;
    end procedure;

    procedure model_step is
      variable e_st, e_saved : dev_state_t;
      variable e_addr, e_cfg : integer;
      variable e_flush : boolean;
    begin
      model(e_st, e_saved, e_addr, e_cfg, e_flush);
      if vbus = '1' and bus_reset = '1' then m_rst := m_rst + 1; end if;
      if e_flush then m_flush := m_flush + 1; end if;
      if e_st = S_SUSPENDED and s_st /= S_SUSPENDED then
        m_susp := m_susp + 1;
      end if;
      if s_st = S_SUSPENDED and e_st /= S_SUSPENDED then
        m_res := m_res + 1;
      end if;
      if e_st = S_CONFIGURED and s_st /= S_CONFIGURED then
        m_cfgd := m_cfgd + 1;
      end if;
      s_st := e_st; s_saved := e_saved; s_addr := e_addr; s_cfg := e_cfg;
    end procedure;

    procedure step is
    begin
      wait for 1 ns;
      check_comb;
      model_step;
      wait until rising_edge(clk);
      wait for 1 ns;
      check(state = st_code(s_st),          "state tracked the model");
      check(saved_state = st_code(s_saved), "saved_state tracked the model");
      check(to_integer(unsigned(dev_address)) = s_addr,
            "dev_address tracked the model");
      check(to_integer(unsigned(dev_config)) = s_cfg,
            "dev_config tracked the model");
      check(n_resets = std_logic_vector(to_unsigned(m_rst, 32)),
            "n_resets matches the model");
      check(n_flushes = std_logic_vector(to_unsigned(m_flush, 32)),
            "n_flushes matches the model");
      check(n_suspends = std_logic_vector(to_unsigned(m_susp, 32)),
            "n_suspends matches the model");
      check(n_resumes = std_logic_vector(to_unsigned(m_res, 32)),
            "n_resumes matches the model");
      check(n_configured = std_logic_vector(to_unsigned(m_cfgd, 32)),
            "n_configured matches the model");
    end procedure;

    procedure idle is
    begin
      bus_reset <= '0'; suspend_req <= '0'; resume_event <= '0';
      set_address <= '0'; set_config <= '0';
    end procedure;

    procedure hard_reset is
    begin
      rst_n <= '0'; vbus <= '0'; idle;
      address_val <= (others => '0'); config_val <= (others => '0');
      wait until rising_edge(clk); wait for 1 ns;
      wait until rising_edge(clk); wait for 1 ns;
      rst_n <= '1'; wait for 1 ns;
      s_st := S_ATTACHED; s_saved := S_POWERED; s_addr := 0; s_cfg := 0;
      m_rst := 0; m_flush := 0; m_susp := 0; m_res := 0; m_cfgd := 0;
    end procedure;

    -- Walk the machine into a chosen state, using only legal transitions.
    procedure goto_state(want : dev_state_t) is
    begin
      hard_reset;
      if want = S_ATTACHED then
        vbus <= '0'; idle; step;
      else
        vbus <= '1'; idle; step;                      -- ATTACHED -> POWERED
        if want /= S_POWERED then
          bus_reset <= '1'; step; idle;               -- -> DEFAULT
          if want = S_ADDRESS or want = S_CONFIGURED
             or want = S_SUSPENDED then
            set_address <= '1';
            address_val <= std_logic_vector(to_unsigned(5, 7));
            step; idle;                               -- -> ADDRESS
          end if;
          if want = S_CONFIGURED or want = S_SUSPENDED then
            set_config <= '1';
            config_val <= std_logic_vector(to_unsigned(1, 4));
            step; idle;                               -- -> CONFIGURED
          end if;
          if want = S_SUSPENDED then
            suspend_req <= '1'; step; idle;           -- -> SUSPENDED
          end if;
        end if;
      end if;
      wait for 1 ns;
      check(state = st_code(want), "goto_state reached the requested state");
    end procedure;

    variable iv : integer;
  begin
    hard_reset;
    check(state = st_code(S_ATTACHED), "reset leaves the device ATTACHED");
    check(endpoints_usable = '0', "with no usable endpoints");

    -- ===== A. EXHAUSTIVE one-step transition sweep =====
    -- 6 states x vbus x bus_reset x suspend_req x resume_event
    --   x set_address x address non-zero x set_config x config non-zero
    -- = 6 x 256 = 1536 transitions: every state against every combination
    -- of the eight control inputs.
    for st0 in 0 to 5 loop
      for a in 0 to 1 loop
        for b in 0 to 1 loop
          for c in 0 to 1 loop
            for d in 0 to 1 loop
              for e in 0 to 1 loop
                for f in 0 to 1 loop
                  for g in 0 to 1 loop
                    for h in 0 to 1 loop
                      goto_state(dev_state_t'val(st0));
                      if a = 1 then vbus <= '1'; else vbus <= '0'; end if;
                      if b = 1 then bus_reset <= '1'; else bus_reset <= '0'; end if;
                      if c = 1 then suspend_req <= '1'; else suspend_req <= '0'; end if;
                      if d = 1 then resume_event <= '1'; else resume_event <= '0'; end if;
                      if e = 1 then set_address <= '1'; else set_address <= '0'; end if;
                      if f = 1 then
                        address_val <= std_logic_vector(to_unsigned(9, 7));
                      else
                        address_val <= (others => '0');
                      end if;
                      if g = 1 then set_config <= '1'; else set_config <= '0'; end if;
                      if h = 1 then
                        config_val <= std_logic_vector(to_unsigned(1, 4));
                      else
                        config_val <= (others => '0');
                      end if;
                      step;
                      n_exh := n_exh + 1;
                      idle;
                    end loop;
                  end loop;
                end loop;
              end loop;
            end loop;
          end loop;
        end loop;
      end loop;
    end loop;
    report "  exhaustive device-FSM sweep: " & integer'image(n_exh)
         & " of 1536 transitions verified" severity note;

    -- ===== B. directed: the enumeration walk, and the reset that undoes it
    hard_reset;

    -- 1. Plug in.
    vbus <= '1'; step; idle;
    check(state = st_code(S_POWERED), "VBUS moves ATTACHED to POWERED");
    check(to_integer(unsigned(dev_address)) = 0, "with no address");
    check(endpoints_usable = '0', "and no usable endpoints");

    -- 2. The host resets the bus. This is the FIRST thing it does.
    bus_reset <= '1'; step; idle;
    check(state = st_code(S_DEFAULT), "a bus reset moves POWERED to DEFAULT");
    check(n_flushes = std_logic_vector(to_unsigned(1, 32)),
          "and flushes every endpoint");

    -- 3. SET_ADDRESS.
    set_address <= '1'; address_val <= std_logic_vector(to_unsigned(7, 7));
    step; idle;
    check(state = st_code(S_ADDRESS),
          "SET_ADDRESS(7) moves DEFAULT to ADDRESS");
    check(to_integer(unsigned(dev_address)) = 7, "with the address applied");
    check(endpoints_usable = '0', "but still only endpoint 0");

    -- 4. SET_CONFIGURATION.
    set_config <= '1'; config_val <= std_logic_vector(to_unsigned(1, 4));
    wait for 1 ns;
    check(ep_flush = '1',
          "SET_CONFIGURATION flushes: it re-creates the endpoints");
    step; idle;
    check(state = st_code(S_CONFIGURED),
          "and moves ADDRESS to CONFIGURED");
    check(endpoints_usable = '1', "NOW the other endpoints exist");
    check(n_flushes = std_logic_vector(to_unsigned(2, 32)),
          "that is the second flush");

    -- 5. THE case. A bus reset from CONFIGURED -- the furthest state --
    --    returns all the way to DEFAULT and flushes everything.
    bus_reset <= '1'; wait for 1 ns;
    check(ep_flush = '1',
          "a bus reset from CONFIGURED flushes the endpoints");
    step; idle;
    check(state = st_code(S_DEFAULT),
          "and returns to DEFAULT from ANY state");
    check(to_integer(unsigned(dev_address)) = 0, "the address is gone");
    check(to_integer(unsigned(dev_config)) = 0, "the configuration is gone");
    check(endpoints_usable = '0', "and the endpoints are not usable");

    -- 6. Suspend from CONFIGURED and resume BACK to CONFIGURED.
    goto_state(S_CONFIGURED);
    check(state = st_code(S_CONFIGURED), "configured again");
    suspend_req <= '1'; step; idle;
    check(state = st_code(S_SUSPENDED), "an idle bus suspends the device");
    check(saved_state = st_code(S_CONFIGURED),
          "remembering where it came from");
    check(endpoints_usable = '0', "a suspended device transfers nothing");
    resume_event <= '1'; step; idle;
    check(state = st_code(S_CONFIGURED),
          "and resume returns to CONFIGURED -- NOT to DEFAULT");
    check(endpoints_usable = '1', "so no re-enumeration is needed");
    check(n_resumes = std_logic_vector(to_unsigned(1, 32)), "one resume");

    -- 7. Suspend from ADDRESS resumes to ADDRESS, not to CONFIGURED.
    goto_state(S_ADDRESS);
    suspend_req <= '1'; step; idle;
    check(saved_state = st_code(S_ADDRESS), "suspended from ADDRESS");
    resume_event <= '1'; step; idle;
    check(state = st_code(S_ADDRESS), "and resumed to ADDRESS");

    -- 8. SET_ADDRESS(0) de-addresses without a bus reset.
    goto_state(S_ADDRESS);
    set_address <= '1'; address_val <= (others => '0'); step; idle;
    check(state = st_code(S_DEFAULT),
          "SET_ADDRESS(0) returns ADDRESS to DEFAULT");
    check(to_integer(unsigned(dev_address)) = 0, "with address zero");

    -- 9. SET_CONFIGURATION(0) un-configures without de-addressing.
    goto_state(S_CONFIGURED);
    set_config <= '1'; config_val <= (others => '0'); step; idle;
    check(state = st_code(S_ADDRESS),
          "SET_CONFIGURATION(0) returns CONFIGURED to ADDRESS");
    check(to_integer(unsigned(dev_address)) = 5, "keeping the address");
    check(endpoints_usable = '0', "and losing the endpoints");

    -- 10. Losing VBUS discards everything.
    goto_state(S_CONFIGURED);
    vbus <= '0'; step; idle;
    check(state = st_code(S_ATTACHED), "losing VBUS returns to ATTACHED");
    check(to_integer(unsigned(dev_address)) = 0, "the address is gone");
    check(to_integer(unsigned(dev_config)) = 0,
          "and the configuration with it");
    check(ep_flush = '0',
          "and an unpowered device does not flush anything");

    -- ===== C. randomised =====
    -- ieee.math_real.uniform is a genuinely different generator from either
    -- Verilog builtin, which is what makes this column independent evidence.
    hard_reset; vbus <= '1';
    for i in 0 to 39999 loop
      rnd(iv, 64); if iv /= 0 then vbus <= '1'; else vbus <= '0'; end if;
      rnd(iv, 24); if iv = 0 then bus_reset <= '1'; else bus_reset <= '0'; end if;
      rnd(iv, 12); if iv = 0 then suspend_req <= '1'; else suspend_req <= '0'; end if;
      rnd(iv, 4);  if iv = 0 then resume_event <= '1'; else resume_event <= '0'; end if;
      rnd(iv, 6);  if iv = 0 then set_address <= '1'; else set_address <= '0'; end if;
      rnd(iv, 128); address_val <= std_logic_vector(to_unsigned(iv, 7));
      rnd(iv, 6);  if iv = 0 then set_config <= '1'; else set_config <= '0'; end if;
      rnd(iv, 16); config_val <= std_logic_vector(to_unsigned(iv, 4));
      step;
    end loop;

    for i in 0 to 5 loop
      check(n_vis(i) > 100, "every device state was visited many times");
    end loop;
    for i in 2 to 4 loop
      check(n_flush_from(i) > 50,
            "endpoint flushes were issued from every configurable state");
    end loop;
    for i in 1 to 4 loop
      check(n_resume_to(i) > 10,
            "resumes returned to every state that can be suspended from");
    end loop;

    report "  REACH: transitions=" & integer'image(n_exh)
         & " | visits: attached=" & integer'image(n_vis(0))
         & " powered=" & integer'image(n_vis(1))
         & " default=" & integer'image(n_vis(2))
         & " address=" & integer'image(n_vis(3))
         & " configured=" & integer'image(n_vis(4))
         & " suspended=" & integer'image(n_vis(5)) severity note;
    report "  FLUSHES from: default=" & integer'image(n_flush_from(2))
         & " address=" & integer'image(n_flush_from(3))
         & " configured=" & integer'image(n_flush_from(4))
         & " suspended=" & integer'image(n_flush_from(5))
         & " | RESUMES to: powered=" & integer'image(n_resume_to(1))
         & " default=" & integer'image(n_resume_to(2))
         & " address=" & integer'image(n_resume_to(3))
         & " configured=" & integer'image(n_resume_to(4)) severity note;
    report "  COUNTERS: resets="
         & integer'image(to_integer(unsigned(n_resets)))
         & " flushes=" & integer'image(to_integer(unsigned(n_flushes)))
         & " suspends=" & integer'image(to_integer(unsigned(n_suspends)))
         & " resumes=" & integer'image(to_integer(unsigned(n_resumes)))
         & " configured=" & integer'image(to_integer(unsigned(n_configured)))
         severity note;
    report "  [VHDL] usb_device_fsm: " & integer'image(errors) & " errors"
         severity note;
    if errors = 0 then
      report "  [VHDL] PASS" severity note;
    else
      report "  [VHDL] FAIL" severity failure;
    end if;
    running <= false;
    wait;
  end process;
end architecture;

12. Exhaustive Verification

MeasureVerilogSystemVerilogVHDL
Exhaustive transitions1536 / 15361536 / 15361536 / 1536
ATTACHED visits241224202452
POWERED visits9634979510010
DEFAULT visits867983488684
ADDRESS visits692172737004
CONFIGURED visits934094318527
SUSPENDED visits867683958985
flushes from DEFAULT391348356
flushes from ADDRESS190919001795
flushes from CONFIGURED184319101630
resumes to POWERED / DEFAULT / ADDRESS / CONFIGURED538 / 482 / 381 / 603538 / 440 / 407 / 583537 / 502 / 392 / 554
bus resets165216121578
endpoint flushes405640803708
ResultPASSPASSPASS

Every state is visited thousands of times, and the testbenches assert it. The resumes-to row is the one that makes §5's claim evidence rather than assertion: resume returned to four different states, several hundred times each. A suite in which every resume happened to come from CONFIGURED would pass identically against a design that always returns to CONFIGURED.

13. Mutation Testing, and a Lesson About Reading the Columns

#MutationVerilogSysVerVHDL
J1a bus reset does not flush the endpoints495464944849391
J2resume always returns to DEFAULT144498142700142360
J3SET_ADDRESS(0) does not return to DEFAULT702506146677778
J4SET_CONFIGURATION(0) does not return to ADDRESS730067109842040
J5a bus reset is ignored in CONFIGURED212909213347202525
J6SET_CONFIGURATION does not flush472664739146704
J7endpoints are usable in ADDRESS too138441454814010
—unmutated baseline000

All seven die in all three languages. But look at J4: 73006 / 71098 / 42040. The VHDL column is 42% below the other two, which by the rule this series has used since Module 18 means the mutation does not mean the same thing in that language — and that rule has been right six times.

It is wrong here, and finding out took one experiment.

J5 is the largest at ~213 000, and it is the bug from §2: a CONFIGURED device that ignores a bus reset. Every cycle after the ignored reset is a mismatch, because the host and the device now disagree about the device's address permanently.

J1 and J6 are the two flush mutations, at ~49 500 and ~47 300 — almost identical, which is the right answer. They remove the two sources of ep_flush, and the suite is equally sensitive to both. A design that flushed on reset but not on SET_CONFIGURATION would be more plausible to ship, and that is precisely why it should not be easier to miss.

J7 is the smallest at ~14 000. It changes one output in one state, and it is worth noting that this is the mutation whose real-world consequence is the least dramatic — firmware is told it may use endpoints that do not exist yet — and the count reflects exactly that narrowness.

14. Debugging Walkthrough: The Device That Loses Its First Packet

The report. A USB data-logger occasionally loses the first record of a session. Not corrupted — absent. It happens perhaps one session in two, and only on sessions that begin after the host software is restarted.

Step 1 — one record, or a burst? Exactly one, always the first. A single missing packet is not a bandwidth or a buffering problem — those lose runs. One packet points at a per-packet mechanism.

Step 2 — what is special about a software restart? The driver reloads, and reloading a USB driver resets the bus. Sessions that begin without a restart do not lose anything. So the correlation is with bus resets, not with the data.

Step 3 — is the reset handled? Read the device state after the reset: DEFAULT, address 0. Correct. Re-enumeration proceeds normally and the device reaches CONFIGURED. The top-level FSM is doing exactly the right thing.

Step 4 — what else holds state? Everything in §3. Read the endpoint's expected toggle immediately after the reset. It is DATA1. The host, per the specification, restarts at DATA0.

Step 5 — what the device does with that. The first packet arrives as DATA0, the endpoint expects DATA1, and Chapter 21.1's rule applies exactly as written: a mismatched toggle is a retransmission, so the endpoint ACKs it and stores nothing. The host sees an ACK and moves on. No error is reported by anything, anywhere.

Step 6 — why one session in two. The toggle is DATA1 after an odd number of packets in the previous session. Half the time it is DATA0 and the reset is harmless.

Step 7 — the fix. ep_flush was not wired to the endpoint's toggle register. Mutation J1, in production, and the whole failure is one missing connection between two correct blocks.

15. UVM: Resetting From Everywhere

15.1 The transaction

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class usb_devstate_item extends uvm_sequence_item;
  `uvm_object_utils(usb_devstate_item)

  rand bit       vbus;
  rand bit       bus_reset;
  rand bit       suspend_req;
  rand bit       resume_event;
  rand bit       set_address;
  rand bit [6:0] address_val;
  rand bit       set_config;
  rand bit [3:0] config_val;

  // A healthy bus: power is on, resets are occasional, requests are rare.
  constraint c_realistic {
    vbus         dist {1 := 63, 0 := 1};
    bus_reset    dist {0 := 23, 1 := 1};
    suspend_req  dist {0 := 11, 1 := 1};
    resume_event dist {0 := 3,  1 := 1};
    set_address  dist {0 := 5,  1 := 1};
    set_config   dist {0 := 5,  1 := 1};
  }

  // The ZERO arguments are legal requests, not errors -- SET_ADDRESS(0) and
  // SET_CONFIGURATION(0) each move the machine BACKWARDS one step, and a
  // uniform draw would present them a fraction of the time.
  constraint c_zero_args_matter {
    address_val dist { 0 := 30, [1:127] := 70 };
    config_val  dist { 0 := 30, [1:15]  := 70 };
  }

  // Two control requests cannot complete in the same cycle on real hardware.
  // Soft, so the adversarial sequence can check the priority anyway.
  constraint c_one_request { soft !(set_address && set_config); }

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

  function string convert2string();
    return $sformatf("vbus=%0b rst=%0b susp=%0b res=%0b sa=%0b/%0d sc=%0b/%0d",
                     vbus, bus_reset, suspend_req, resume_event,
                     set_address, address_val, set_config, config_val);
  endfunction
endclass

15.2 Sequences

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// THE sequence for this chapter. It walks the device all the way to
// CONFIGURED and THEN resets the bus -- the transition a design written
// around the enumeration walk is most likely to have missed, because it is
// the one that goes backwards from the state everything else is aiming at.
class reset_from_configured_seq extends uvm_sequence #(usb_devstate_item);
  `uvm_object_utils(reset_from_configured_seq)
  function new(string name = "reset_from_configured_seq"); super.new(name); endfunction

  task body();
    repeat (300) begin
      usb_devstate_item it;

      // walk: reset -> address -> configure
      for (int stage = 0; stage < 3; stage++) begin
        it = usb_devstate_item::type_id::create("it");
        start_item(it);
        it.c_realistic.constraint_mode(0);
        it.c_zero_args_matter.constraint_mode(0);
        if (!it.randomize() with {
              vbus == 1; suspend_req == 0; resume_event == 0;
              bus_reset   == (stage == 0);
              set_address == (stage == 1);
              set_config  == (stage == 2);
              address_val != 0;
              config_val  != 0; })
          `uvm_error("RAND", "walk randomize failed")
        finish_item(it);
      end

      // ...and now reset from CONFIGURED.
      it = usb_devstate_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { vbus == 1; bus_reset == 1;
                                 suspend_req == 0; resume_event == 0; })
        `uvm_error("RAND", "reset randomize failed")
      finish_item(it);
    end
  endtask
endclass

// Suspend and resume from EVERY state that can be suspended from. A suite in
// which every resume happens to come from CONFIGURED passes identically
// against a design that always returns to CONFIGURED.
class suspend_resume_everywhere_seq extends uvm_sequence #(usb_devstate_item);
  `uvm_object_utils(suspend_resume_everywhere_seq)
  function new(string name = "suspend_resume_everywhere_seq"); super.new(name); endfunction

  rand int unsigned depth;           // 0 = DEFAULT, 1 = ADDRESS, 2 = CONFIGURED
  constraint c_depth { depth inside {[0:2]}; }

  task body();
    repeat (400) begin
      usb_devstate_item it;
      int unsigned d = $urandom_range(0, 2);

      // walk to the chosen depth
      for (int stage = 0; stage <= d; stage++) begin
        it = usb_devstate_item::type_id::create("it");
        start_item(it);
        it.c_realistic.constraint_mode(0);
        it.c_zero_args_matter.constraint_mode(0);
        if (!it.randomize() with {
              vbus == 1; suspend_req == 0; resume_event == 0;
              bus_reset   == (stage == 0);
              set_address == (stage == 1);
              set_config  == (stage == 2);
              address_val != 0; config_val != 0; })
          `uvm_error("RAND", "depth randomize failed")
        finish_item(it);
      end

      // suspend, then resume -- and the scoreboard checks we came BACK here
      it = usb_devstate_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { vbus == 1; bus_reset == 0;
                                 suspend_req == 1; resume_event == 0; })
        `uvm_error("RAND", "suspend randomize failed")
      finish_item(it);

      it = usb_devstate_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { vbus == 1; bus_reset == 0;
                                 suspend_req == 0; resume_event == 1; })
        `uvm_error("RAND", "resume randomize failed")
      finish_item(it);
    end
  endtask
endclass

// The backward requests: SET_ADDRESS(0) and SET_CONFIGURATION(0), which move
// the machine one step back without a bus reset.
class backward_requests_seq extends uvm_sequence #(usb_devstate_item);
  `uvm_object_utils(backward_requests_seq)
  function new(string name = "backward_requests_seq"); super.new(name); endfunction

  task body();
    repeat (400) begin
      usb_devstate_item it = usb_devstate_item::type_id::create("it");
      start_item(it);
      it.c_zero_args_matter.constraint_mode(0);
      if (!it.randomize() with { vbus == 1; bus_reset == 0;
                                 suspend_req == 0; resume_event == 0;
                                 (set_address || set_config) == 1;
                                 address_val == 0; config_val == 0; })
        `uvm_error("RAND", "backward randomize failed")
      finish_item(it);
    end
  endtask
endclass

15.3 The scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class usb_devstate_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(usb_devstate_scoreboard)

  uvm_analysis_imp #(usb_devstate_mon_item, usb_devstate_scoreboard) ap;

  // The scoreboard keeps its OWN copy of every piece of retained state.
  dev_state_e  sb_state, sb_saved;
  bit [6:0]    sb_addr;
  bit [3:0]    sb_cfg;

  int unsigned n_reset_from[6];     // resets seen from each state
  int unsigned n_resume_to[6];      // resumes landing in each state
  int unsigned n_flush, n_missing_flush;

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

  function void write(usb_devstate_mon_item t);
    dev_state_e prev = sb_state;

    // ---- THE property. A bus reset with power MUST flush. ----
    if (t.vbus && t.bus_reset) begin
      if (!t.ep_flush)
        `uvm_error("STALE_STATE",
          "a bus reset did not assert ep_flush -- every endpoint toggle, halt, buffer and pointer survives into the new session, and the first packet of that session is silently dropped")
      n_reset_from[int'(prev)]++;
    end

    // ---- SET_CONFIGURATION re-creates the endpoints, so it flushes too ----
    if (t.vbus && t.set_config
        && (prev inside {S_ADDRESS, S_CONFIGURED})) begin
      if (!t.ep_flush)
        `uvm_error("STALE_STATE",
          "a SET_CONFIGURATION did not flush the endpoints it re-created")
    end

    // ---- ...and nothing ELSE flushes. A spurious flush loses a packet ----
    if (t.ep_flush) begin
      bit legitimate = t.vbus
                       && (t.bus_reset
                           || (t.set_config
                               && (prev inside {S_ADDRESS, S_CONFIGURED})));
      if (!legitimate)
        `uvm_error("SPURIOUS_FLUSH",
          "the endpoints were flushed with no reset and no SET_CONFIGURATION")
      n_flush++;
    end

    // ---- Advance the scoreboard's own state, by the spec's rules ----
    if (!t.vbus) begin
      sb_state = S_ATTACHED; sb_saved = S_POWERED;
      sb_addr = 0; sb_cfg = 0;
    end else if (t.bus_reset) begin
      sb_state = S_DEFAULT;  sb_saved = S_DEFAULT;
      sb_addr = 0; sb_cfg = 0;
    end else if (prev == S_ATTACHED) begin
      sb_state = S_POWERED;  sb_saved = S_POWERED;
    end else if (prev == S_SUSPENDED) begin
      if (t.resume_event) begin
        // THE resume property: we return to where we suspended FROM.
        sb_state = sb_saved;
        n_resume_to[int'(sb_saved)]++;
      end
    end else if (t.suspend_req) begin
      sb_saved = prev; sb_state = S_SUSPENDED;
    end else if (t.set_address && (prev inside {S_DEFAULT, S_ADDRESS})) begin
      sb_addr  = t.address_val;
      sb_state = (t.address_val != 0) ? S_ADDRESS : S_DEFAULT;
    end else if (t.set_config && (prev inside {S_ADDRESS, S_CONFIGURED})) begin
      sb_cfg   = t.config_val;
      sb_state = (t.config_val != 0) ? S_CONFIGURED : S_ADDRESS;
    end

    // ---- Compare, from independently maintained state ----
    if (t.state_after !== sb_state)
      `uvm_error("STATE",
        $sformatf("state=%s, scoreboard=%s", t.state_after.name(),
                  sb_state.name()))
    if (t.addr_after !== sb_addr)
      `uvm_error("ADDR", $sformatf("address=%0d, scoreboard=%0d",
                                   t.addr_after, sb_addr))
    if (t.cfg_after !== sb_cfg)
      `uvm_error("CFG", $sformatf("config=%0d, scoreboard=%0d",
                                  t.cfg_after, sb_cfg))

    // ---- Structural invariants ----
    if (t.endpoints_usable && (t.state_after != S_CONFIGURED))
      `uvm_error("ENDPOINTS",
        "endpoints reported usable outside CONFIGURED -- firmware may transfer on endpoints that do not exist")
    if ((t.state_after == S_DEFAULT) && (t.addr_after != 0))
      `uvm_error("ADDR", "DEFAULT with a non-zero address")
    if ((t.state_after == S_SUSPENDED) && (t.saved_after == S_SUSPENDED))
      `uvm_error("SUSPEND", "SUSPENDED remembers SUSPENDED -- resume never leaves")
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("SB", $sformatf("flushes=%0d", n_flush), UVM_LOW)

    // A reset must have been presented from EVERY state, including the one
    // it is most likely to have been forgotten from.
    foreach (n_reset_from[i])
      if (n_reset_from[i] == 0)
        `uvm_error("COVERAGE",
          $sformatf("no bus reset was ever presented in state %0d", i))
    if (n_reset_from[int'(S_CONFIGURED)] == 0)
      `uvm_error("COVERAGE",
        "no bus reset from CONFIGURED -- the transition most likely to be missing is untested")

    // And resume must have returned to more than one state.
    begin
      int distinct = 0;
      foreach (n_resume_to[i]) if (n_resume_to[i] > 0) distinct++;
      if (distinct < 2)
        `uvm_error("COVERAGE",
          "every resume returned to the same state -- a design that always resumes to one state would pass this run")
    end
  endfunction
endclass

The last check in report_phase is the one worth stealing. "Resume returns to the state it suspended from" is untestable if every suspend in the run came from the same state — the run passes identically against a design that ignores saved_state entirely. Counting how many distinct states resume landed in turns that from an assumption into a measurement.

15.4 Functional coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup dev_fsm_cg with function sample(
    dev_state_e st, dev_state_e saved, bit vbus, bit rst, bit susp,
    bit res, bit sa, bit [6:0] av, bit sc, bit [3:0] cv, bit flush);

  cp_state : coverpoint st {
    bins all[] = {S_ATTACHED, S_POWERED, S_DEFAULT, S_ADDRESS,
                  S_CONFIGURED, S_SUSPENDED};
  }

  cp_reset : coverpoint rst { bins none = {0}; bins reset = {1}; }

  // THE cross. A bus reset from EVERY state, and the bin that matters is
  // (CONFIGURED, reset) -- the transition a design built around the
  // enumeration walk is most likely to be missing.
  x_reset_from_anywhere : cross cp_state, cp_reset;

  // Where a resume LANDS. Four reachable values, and a design that ignores
  // saved_state closes only one of them.
  cp_resume_to : coverpoint saved iff (st == S_SUSPENDED && res) {
    bins to_powered    = {S_POWERED};
    bins to_default    = {S_DEFAULT};
    bins to_address    = {S_ADDRESS};
    bins to_configured = {S_CONFIGURED};
  }

  // The zero arguments, which are legal requests that move BACKWARDS.
  cp_addr_zero : coverpoint (av == 0) iff (sa) {
    bins de_address = {1};
    bins address    = {0};
  }
  cp_cfg_zero : coverpoint (cv == 0) iff (sc) {
    bins un_configure = {1};
    bins configure    = {0};
  }

  // Both backward requests, from both states in which each is legal.
  x_backward : cross cp_state, cp_addr_zero;
  x_unconfig : cross cp_state, cp_cfg_zero;

  // Every source of a flush, from every state.
  cp_flush : coverpoint flush { bins flushing = {1}; bins quiet = {0}; }
  x_flush_state : cross cp_flush, cp_state;
endgroup

16. SystemVerilog Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module usb_device_fsm_sva
  import usb_devfsm_pkg::*;
(
  input logic       clk,
  input logic       rst_n,
  input logic       vbus,
  input logic       bus_reset,
  input logic       suspend_req,
  input logic       resume_event,
  input logic       set_address,
  input logic [6:0] address_val,
  input logic       set_config,
  input logic [3:0] config_val,
  input dev_state_e state,
  input logic [6:0] dev_address,
  input logic [3:0] dev_config,
  input logic       ep_flush,
  input logic       endpoints_usable,
  input logic       suspended,
  input dev_state_e saved_state
);
  default clocking cb @(posedge clk); endclocking
  default disable iff (!rst_n);

  // ---- 1. THE property. A bus reset with power flushes every endpoint. ----
  property p_reset_flushes;
    (vbus && bus_reset) |-> ep_flush;
  endproperty
  a_reset_flushes : assert property (p_reset_flushes)
    else $error("a bus reset did not flush -- stale toggles and halts survive into the new session");

  // ---- 2. ...and returns to DEFAULT, from ANY state. ----
  property p_reset_returns_to_default;
    (vbus && bus_reset) |=> ((state == S_DEFAULT) && (dev_address == 7'd0)
                             && (dev_config == 4'd0));
  endproperty
  a_reset_returns_to_default : assert property (p_reset_returns_to_default)
    else $error("a bus reset did not return the device to DEFAULT from %s",
                $past(state.name()));

  // ---- 3. SET_CONFIGURATION flushes too: it re-creates the endpoints. ----
  property p_setconfig_flushes;
    (vbus && set_config && (state inside {S_ADDRESS, S_CONFIGURED}))
      |-> ep_flush;
  endproperty
  a_setconfig_flushes : assert property (p_setconfig_flushes);

  // ---- 4. Nothing ELSE flushes. A spurious flush loses a packet just as
  // ----    surely as a missing one.
  property p_no_spurious_flush;
    ep_flush |-> (vbus && (bus_reset
                           || (set_config
                               && (state inside {S_ADDRESS, S_CONFIGURED}))));
  endproperty
  a_no_spurious_flush : assert property (p_no_spurious_flush)
    else $error("the endpoints were flushed with no reset and no SET_CONFIGURATION");

  // ---- 5. THE resume property. Resume returns to where we suspended. ----
  property p_resume_returns_to_saved;
    ((state == S_SUSPENDED) && resume_event && vbus && !bus_reset)
      |=> (state == $past(saved_state));
  endproperty
  a_resume_returns_to_saved : assert property (p_resume_returns_to_saved)
    else $error("resume landed in %s, expected %s -- the device will re-enumerate after every idle period",
                state.name(), $past(saved_state.name()));

  // ---- 6. Endpoints beyond endpoint 0 exist only when CONFIGURED. ----
  property p_endpoints_only_when_configured;
    endpoints_usable == (state == S_CONFIGURED);
  endproperty
  a_endpoints_only_when_configured :
    assert property (p_endpoints_only_when_configured);

  // ---- 7. DEFAULT means address zero, by definition. ----
  property p_default_is_address_zero;
    (state == S_DEFAULT) |-> (dev_address == 7'd0);
  endproperty
  a_default_is_address_zero : assert property (p_default_is_address_zero);

  // ---- 8. CONFIGURED means a non-zero configuration. ----
  property p_configured_has_a_config;
    (state == S_CONFIGURED) |-> (dev_config != 4'd0);
  endproperty
  a_configured_has_a_config : assert property (p_configured_has_a_config);

  // ---- 9. Nothing is retained without power. ----
  property p_unpowered_retains_nothing;
    !vbus |=> ((state == S_ATTACHED) && (dev_address == 7'd0)
               && (dev_config == 4'd0));
  endproperty
  a_unpowered_retains_nothing : assert property (p_unpowered_retains_nothing);

  // ---- 10. SUSPENDED always has somewhere real to return to. ----
  property p_saved_is_never_suspended;
    (state == S_SUSPENDED) |-> (saved_state != S_SUSPENDED);
  endproperty
  a_saved_is_never_suspended : assert property (p_saved_is_never_suspended)
    else $error("SUSPENDED remembers SUSPENDED -- resume would never leave");

  // ---- 11. A flush never happens with no power. ----
  property p_no_flush_unpowered;
    !vbus |-> !ep_flush;
  endproperty
  a_no_flush_unpowered : assert property (p_no_flush_unpowered);

  // ---- Cover: a reset from EVERY state, and resume to more than one. ----
  c_reset_from_configured :
    cover property ((vbus && bus_reset && (state == S_CONFIGURED)));
  c_reset_from_suspended  :
    cover property ((vbus && bus_reset && (state == S_SUSPENDED)));
  c_resume_to_configured  :
    cover property (((state == S_SUSPENDED) && resume_event
                     && (saved_state == S_CONFIGURED)));
  c_resume_to_address     :
    cover property (((state == S_SUSPENDED) && resume_event
                     && (saved_state == S_ADDRESS)));
  c_de_address  : cover property ((set_address && (address_val == 7'd0)));
  c_un_config   : cover property ((set_config && (config_val == 4'd0)));
endmodule

bind usb_device_fsm usb_device_fsm_sva u_sva (.*);

17. Common Misconceptions

"A bus reset only happens at plug-in." It happens whenever the host decides — driver reload, suspend/resume at the hub, error recovery upstream. A working, configured, actively-transferring device can be reset without warning.

"Resetting the state machine is resetting the device." The state machine is the easiest part. Toggles, halts, buffers and pointers all hold session state, and all of them have to go.

"Suspend is another state in the sequence." It is orthogonal: something that happens to a state. Resume returns to whichever state it left, or the device re-enumerates after every idle period.

"SET_ADDRESS(0) is an invalid request." It is how a host de-addresses a device without a bus reset. Same for SET_CONFIGURATION(0). Both move the machine one step backwards.

"Endpoints exist as soon as the device is addressed." Only endpoint 0 does. The rest are created by SET_CONFIGURATION, which is why it flushes them.

"A SET_CONFIGURATION to the configuration already selected is a no-op." It still re-creates the endpoints and still flushes. The specification is explicit, and firmware that reconfigures to the same value relies on the reset of the toggles.

"An extra flush is harmless — it just resets some toggles." It loses a packet, exactly as a missing flush does. See SVA property 4.

18. Exercises

1. Remove bus_reset from ep_flush (mutation J1) and predict which safety property fails first. Then explain why the count is ~49 500 rather than the ~1600 bus resets the suite generates.

2. J4's columns differ by 42% and it means nothing. Identify which of the other six mutations are also history-dependent, predict their seed variance, and measure one to check.

3. Add a SET_INTERFACE request, which resets the toggles of the endpoints in one interface but not the others. What does ep_flush become, and which of the eleven SVA properties need changing?

4. Properties 1 and 4 pin ep_flush from both sides. Find the pair of properties that pin endpoints_usable the same way, and show that property 6 alone already does the job — then explain what is different about ep_flush that needs two.

5. The scoreboard counts how many distinct states resume landed in. Write the equivalent guard for bus resets, and say what it would have caught in a suite that only ever reset from POWERED.

6. Wire this block to Chapter 21.1's usb_endpoint_ctrl through ep_flush → ep_reset and run 21.1's exhaustive sweep with resets injected from this FSM. Which of 21.1's seven mutations become easier to kill, and which become harder?

19. Summary

IdeaWhy it matters
Six states, and enumeration walks five of themATTACHED → POWERED → DEFAULT → ADDRESS → CONFIGURED
A bus reset arrives in any stateincluding CONFIGURED, which is the one that gets missed
...and always returns to DEFAULTaddress 0, no configuration
Returning to DEFAULT is not enoughtoggles, halts, buffers and pointers all hold session state
One ep_flush signal reaching every endpointa traceable net, not a convention
SET_CONFIGURATION flushes tooit re-creates the endpoints
Nothing else may flusha spurious flush loses a packet too
Suspend is orthogonal and must rememberresume returns to where it suspended from
SET_ADDRESS(0) / SET_CONFIGURATION(0) are legalthey move the machine backwards
Endpoints beyond 0 exist only in CONFIGURED
1536-transition exhaustive verificationevery state × every input combination
7 mutations, all killed in 3 languagesand one apparent outlier that was pure seed variance

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o df_v.out df_v.v df_v_tb.v && ./df_v.out
SystemVerilogiverilog -g2012 -o df_sv.out df_sv.sv df_sv_tb.sv && ./df_sv.out
VHDL-2008 analysenvc --std=2008 -a df_vhdl.vhd df_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_df_vhdl
VHDL-2008 runnvc --std=2008 -r tb_df_vhdl
One mutationiverilog -g2005 -DMUT_J1 -o mm df_v_mut.v df_v_tb.v && ./mm

All three implementations pass with 0 errors: 1536 of 1536 exhaustive transitions, 40 000 randomised cycles, every state visited and asserted visited.

20. Module 21 Complete

Five chapters, five synthesisable blocks, fifteen implementations, thirty-five mutations — all killed in all three languages.

ChapterBlockThe structural idea
21.1usb_endpoint_ctrlone bit distinguishes a retransmission from new data
21.2usb_ep_pingponga FIFO of packets, because a ZLP is a packet
21.3usb_descriptor_enginethe host says how much it will take
21.4usb_packet_enginewrite provisionally; the CRC arrives last
21.5usb_device_fsma reset must reach every register that holds session state

Read together, they are five answers to the same question: what does a device do when it does not yet know something it needs?

The endpoint does not know whether a packet is new — so it keeps one bit that makes the question answerable. The FIFO does not know how many bytes a transfer contains — so it stores boundaries rather than bytes. The descriptor engine does not know how much the host wants — so it asks the request instead of assuming. The packet engine does not know whether the payload is good — so it writes provisionally and commits at the end. And the device FSM does not know when the host will give up on it — so it is ready to be reset at any instant, all the way down.

None of the five is a performance optimisation. Every one of them exists because information arrives later than the decision that needs it, and the difference between a device that works and one that mostly works is which of those five gaps you noticed.

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.