Skip to content
VLSI Mentor

USB · Module 18

Port Management

Three uncoordinated sources move a hub port and collide in the same cycle — and the host, whose information is stale by construction, is the one that loses.

Chapter 18.1 built the repeater and left one input conspicuously undefined. port_enabled decides which ports are on the bus in either direction — and nothing in that chapter said where it comes from.

It comes from here: a state machine, one instance per downstream port, living inside the hub controller.

A port is not a bit. It is unpowered, empty, attached but silent, being reset, carrying traffic, or idled — and it is pushed between those by three sources that do not coordinate with each other and can act in the same cycle. Deciding who wins is the whole chapter.

1. Six States, and What Each One Means

StatePoweredPasses trafficMeaning
Powered-offnonono power on the port; nothing can be sensed
Disconnectedyesnopowered, nothing attached
Disabledyesnoa device is attached and the hub will not carry its traffic
Resettingyesnothe hub is driving reset downstream
Enabledyesyesthe only state that carries packets
Suspendedyesnoenabled, but idled

Exactly one state passes traffic. port_enabled to 18.1's repeater is asserted in Enabled and nowhere else — notably not in Resetting. A port being reset is emphatically not carrying packets, and a design that leaves it enabled through the reset will broadcast host traffic into a device that is being held in reset.

Powered-off is not merely "off". With no power on the port there is no pull-up on the device side to sense, so attached means nothing in that state. This is why the FSM treats detection as meaningful only while powered, and why powering a port that already has a device in it must report a connect event — the host has not seen that device yet.

2. There Is No Enable Command

Here is the rule that surprises people, and it is worth stating before the diagram.

A host cannot command a port to become enabled. There is no such operation. A port reaches Enabled by exactly two routes:

  1. completing a reset, or
  2. resuming from Suspended.

The host asks for a reset and waits; the enable is a consequence of the reset finishing. The reason is that a reset is what puts the device into a known state — address zero, default configuration — and enabling a port whose device has not been reset would put an unknown device on the bus with an unknown address.

§14 is about how this rule, stated slightly too strongly in the testbench, produced eight failures against a correct design.

3. Three Sources, and Why the Host Loses

Three things move a port, and they do not coordinate:

The host, issuing port commands — SetPortFeature / ClearPortFeature control transfers to the hub controller (18.1 §1).

The device below, attaching and detaching whenever a human moves a cable.

The hub itself, disabling a port for cause — 18.1's babble isolation, and over-current.

They can all act in the same cycle, and the precedence is not "host wins":

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   detach   >   over-current   >   babble   >   host command

The reason is informational, not political. The host's command was composed from a port status it read some time ago: it issued a control transfer, which took microseconds to arrive, based on a status poll that happened before that. The host is the only one of the three that can be wrong about the present. A detach is happening now. An over-current is happening now. Babble was detected this cycle. A host command is a statement about a port as it was.

So a host suspend arriving in the same cycle the repeater isolates the port for babbling loses. The port goes to Disabled, not Suspended — and the host finds out through the change bits (§6) on its next poll.

4. The State Machine, Drawn

A state machine diagram of a USB hub downstream port with six states. Powered-off is the start state at the upper left. A SetPortFeature POWER command moves it right to Disconnected, where the port is powered but empty. When a device attaches, the port moves right to Disabled: the device is present but the hub will not carry its traffic. From Disabled, a SetPortFeature RESET command moves the port down to Resetting, where the hub drives reset downstream for a fixed duration. When that duration elapses the port moves left into Enabled, and this is the only route by which a port becomes enabled, because there is no enable command. Enabled is the only state that asserts port enabled to the repeater. From Enabled a SetPortFeature SUSPEND command moves the port left to Suspended, and a ClearPortFeature SUSPEND resumes it back to Enabled, which is the second and only other route into Enabled. Also from Enabled, either a babble fault detected by the repeater or an explicit ClearPortFeature ENABLE command returns the port up to Disabled; when a babble and a host command arrive in the same cycle the babble wins. A detach returns the port from Disabled to Disconnected, representing a rule that applies from every powered state, and an over-current takes the port from Suspended back to Powered-off, likewise representing a rule that applies from every powered state.Powered-offDisconnectedDisabledResettingEnabledSuspendedSetFeature(POWER)SetFeature(POWER)attachattachSetFeature(RESET)SetFeature(RESET)reset completeresetcompleteSetFeature(SUSPEND)SetFeature(SUSPEND)ClrFeature(SUSPEND)ClrFeature(SUSPEND)babble / ClrFeature(ENABLE)babble /ClrFeature(ENABLE)detach (any powered state)detach (any powered state)detach (anypowered…over-current (any state)over-current(any state)
Figure 1 — the per-port state machine. Enabled is the only state that asserts port_enabled to 18.1's repeater. Two transitions are drawn as representative of rules that apply from every powered state: a detach returns the port to Disconnected from anywhere, and an over-current takes it to Powered-off from anywhere. The babble edge is the collision §3 is about.

The two routes into Enabled are the diagram's main assertion. Nothing enters Enabled from Disabled, Disconnected or Powered-off directly, and §13 checks that as a history invariant on every transition of a 20 000-cycle run.

5. Change Bits: Set by Hardware, Cleared by the Host

A hub cannot interrupt the host. Chapter 2.6's single-master model means the hub only ever answers, so everything that happens on a port must wait in a register until the host asks.

Five change bits, each a distinct fact: connect, enable, suspend, reset, over-current. Set by hardware when the event occurs; cleared only when the host explicitly acknowledges. Chapter 18.3 builds the reporting mechanism on top of them.

Connect-change is an edge, not a level — and getting this wrong is mutation M7's 22 009 errors. Driving it from "the port is empty" re-sets the bit every cycle an empty port sits idle, so the host can never clear it. A change bit that cannot be cleared reports nothing: it is indistinguishable from a stuck signal.

And the collision that matters: what if an event sets a bit in the same cycle the host clears it?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      change <= (change & ~(chg_clear ? chg_clear_mask : 5'd0)) | set_chg;

SET wins, structurally — the clear is applied first and the set after, so a same-cycle event survives. The alternative loses the event with no trace anywhere. This ordering loses an acknowledgement instead, and the host discovers that on its next poll and clears it again. Losing an ack costs one poll; losing an event costs a device that never gets enumerated.

6. The Hardware, Before Any Language

State retained: the port state, a reset duration counter, the previous attached value, and the five change bits.

Reset duration is a parameter, not a constant. The real value is milliseconds of wall time; the testbenches use 4 cycles so a 784-point sweep finishes quickly, and the design must not care.

port_enabled is asserted in Enabled only. Not Resetting, not Suspended.

attached is qualified by power. With the port unpowered, attached is not merely ignored — it is meaningless, and the stored history resets so that powering a populated port reports a connect event.

The precedence chain runs before the state table, in the order of §3, so that every state inherits it without restating it six times. Chapter 16.2 is the reason: a condition written once is a condition that cannot disagree with itself.

7. Verilog-2005

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_port_fsm -- the per-port state machine inside a hub's controller.
//
// Chapter 18.1 built the repeater and left `port_enabled` as an INPUT. This
// module is what drives it: one instance per downstream port.
//
// Three UNCOORDINATED sources move a port, and the whole difficulty of the
// block is that they can act in the same cycle:
//
//   1. the HOST, issuing port commands as control transfers to the hub
//      controller (SetPortFeature / ClearPortFeature);
//   2. the DEVICE below, attaching and detaching whenever it likes;
//   3. the HUB ITSELF, disabling a port for cause -- chapter 18.1's babble
//      isolation, and over-current.
//
// The host is the slowest of the three and the only one that can be wrong
// about the present, because its command was composed from a port status it
// read some time ago. So the precedence is NOT "host wins":
//
//      detach  >  over-current  >  babble  >  host command
//
// Physical reality outranks the hub's own faults, which outrank software.
//
// The other rule worth stating before the code: THERE IS NO ENABLE COMMAND.
// A port becomes Enabled only by completing a RESET. A host that wants a
// port enabled asks for a reset and waits; the enable is a side effect of
// the reset finishing, never something it can write directly.
module usb_port_fsm #(
  parameter integer RESET_CYCLES = 20      // duration of the drive-reset
) (
  input  wire       clk,
  input  wire       rst_n,

  // --- the host, through the hub controller (chapter 18.3) ---
  input  wire       cmd_valid,
  input  wire [2:0] cmd,
  // --- the device below ---
  input  wire       attached,              // physical presence detect
  // --- the hub itself ---
  input  wire       babble,                // 18.1: repeater isolated this port
  input  wire       overcurrent,

  output reg  [2:0] state,
  output wire       port_powered,
  output wire       port_enabled,          // --> chapter 18.1's repeater
  output wire       resetting,

  // --- sticky change bits, set by hardware, cleared by the host (18.3) ---
  input  wire       chg_clear,             // host acknowledges
  input  wire [4:0] chg_clear_mask,
  output reg  [4:0] change                 // {oc, reset, suspend, enable, connect}
);
  // Port states. USB 2.0 names them in section 11.5; these are the six that
  // carry distinct behaviour here.
  localparam [2:0] P_POWERED_OFF  = 3'd0,
                   P_DISCONNECTED = 3'd1,  // powered, nothing attached
                   P_DISABLED     = 3'd2,  // attached, not passing traffic
                   P_RESETTING    = 3'd3,
                   P_ENABLED      = 3'd4,
                   P_SUSPENDED    = 3'd5;

  localparam [2:0] CMD_NONE        = 3'd0,
                   CMD_SET_POWER   = 3'd1,
                   CMD_CLR_POWER   = 3'd2,
                   CMD_SET_RESET   = 3'd3,
                   CMD_SET_SUSPEND = 3'd4,
                   CMD_CLR_SUSPEND = 3'd5,  // resume
                   CMD_CLR_ENABLE  = 3'd6;  // disable, by request

  // change bit positions
  localparam integer C_CONNECT = 0, C_ENABLE = 1, C_SUSPEND = 2,
                     C_RESET   = 3, C_OC     = 4;

  assign port_powered = (state != P_POWERED_OFF);
  // ONLY the Enabled state passes traffic. Suspended does not, Resetting
  // does not -- a port being reset is emphatically not carrying packets.
  assign port_enabled = (state == P_ENABLED);
  assign resetting    = (state == P_RESETTING);

  reg [31:0] rst_cnt;

  // A command is only a command when it is valid. Decoding `cmd` without
  // qualifying it makes CMD_NONE (3'd0) indistinguishable from an idle bus.
  wire       c       = cmd_valid;
  wire       is_pwr_off = c && (cmd == CMD_CLR_POWER);
  wire       is_pwr_on  = c && (cmd == CMD_SET_POWER);
  wire       is_reset   = c && (cmd == CMD_SET_RESET);
  wire       is_susp    = c && (cmd == CMD_SET_SUSPEND);
  wire       is_resume  = c && (cmd == CMD_CLR_SUSPEND);
  wire       is_disable = c && (cmd == CMD_CLR_ENABLE);

  // A detach is only observable while the port is powered: with the port
  // powered off there is no pull-up to sense, so `attached` means nothing.
  wire       detached  = port_powered && !attached;

  // Connect-change is an EDGE, not a level. Setting it from `detached`
  // directly would re-set it every cycle the port sits empty, and the host
  // would never be able to clear it -- a change bit that cannot be cleared
  // is a change bit that reports nothing.
  reg        attached_q;
  wire       conn_changed = port_powered && (attached != attached_q);

  // The change register: set by hardware here, cleared by the host through
  // chg_clear/chg_clear_mask. Chapter 18.3 is about what happens when both
  // occur in the same cycle -- SET WINS, and that is built in below.
  reg [4:0] set_chg;

  always @* begin
    set_chg = 5'd0;
    if (state != P_POWERED_OFF) begin
      if (overcurrent)                       set_chg[C_OC]      = 1'b1;
      if (conn_changed)                      set_chg[C_CONNECT] = 1'b1;
      // a port leaving Enabled for a reason the host did not ask for
      if (state == P_ENABLED && babble && !overcurrent && !detached)
        set_chg[C_ENABLE] = 1'b1;
      if (state == P_RESETTING && rst_cnt == RESET_CYCLES-1 && !detached
          && !overcurrent)                   set_chg[C_RESET]   = 1'b1;
      if (state == P_SUSPENDED && is_resume && !detached && !overcurrent)
        set_chg[C_SUSPEND] = 1'b1;
    end
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state      <= P_POWERED_OFF;
      rst_cnt    <= 32'd0;
      change     <= 5'd0;
      attached_q <= 1'b0;
    end else begin
      // While the port is unpowered there is nothing to sense, so the
      // history resets: powering a port that already has a device attached
      // must report a connect change, because the host has not seen it yet.
      attached_q <= (state != P_POWERED_OFF) ? attached : 1'b0;
      // ---- change bits: SET beats CLEAR, always (chapter 18.3) ----
      // An event that arrives in the same cycle the host clears the bit must
      // survive. Clearing first and setting after would lose the event with
      // no trace; this ordering loses an acknowledgement instead, which the
      // host discovers on its next poll.
      change <= (change & ~(chg_clear ? chg_clear_mask : 5'd0)) | set_chg;

      // ---- the state, in strict precedence order ----
      if (detached) begin
        // Physical reality first. A device that is gone is gone, whatever
        // the host just asked for and whatever the repeater thinks.
        state   <= P_DISCONNECTED;
        rst_cnt <= 32'd0;
      end else if (overcurrent && state != P_POWERED_OFF) begin
        // The port is drawing more than it may. Removing power is not a
        // policy choice; it is the only way to stop it.
        state   <= P_POWERED_OFF;
        rst_cnt <= 32'd0;
      end else begin
        case (state)
          P_POWERED_OFF:
            if (is_pwr_on) state <= P_DISCONNECTED;

          P_DISCONNECTED: begin
            if (is_pwr_off)    state <= P_POWERED_OFF;
            else if (attached) state <= P_DISABLED;
          end

          P_DISABLED: begin
            if (is_pwr_off)     state <= P_POWERED_OFF;
            else if (is_reset) begin
              state   <= P_RESETTING;
              rst_cnt <= 32'd0;
            end
            // NOTE: no command moves a port from Disabled to Enabled. The
            // only route is through a reset.
          end

          P_RESETTING: begin
            if (is_pwr_off) begin
              state <= P_POWERED_OFF; rst_cnt <= 32'd0;
            end else if (rst_cnt == RESET_CYCLES-1) begin
              // Reset complete: THIS is where a port becomes enabled.
              state   <= P_ENABLED;
              rst_cnt <= 32'd0;
            end else begin
              rst_cnt <= rst_cnt + 32'd1;
            end
          end

          P_ENABLED: begin
            if (is_pwr_off)      state <= P_POWERED_OFF;
            // BABBLE OUTRANKS THE HOST'S COMMAND. If the repeater isolated
            // this port in the same cycle the host asked to suspend it, the
            // port goes to Disabled, not Suspended -- the host composed that
            // command from a status read before the fault existed.
            else if (babble)     state <= P_DISABLED;
            else if (is_disable) state <= P_DISABLED;
            else if (is_susp)    state <= P_SUSPENDED;
          end

          P_SUSPENDED: begin
            if (is_pwr_off)      state <= P_POWERED_OFF;
            else if (is_resume)  state <= P_ENABLED;
            else if (is_disable) state <= P_DISABLED;
          end

          default: state <= P_POWERED_OFF;
        endcase
      end
    end
  end
endmodule

The c = cmd_valid qualification is not cosmetic. CMD_NONE is 3'd0, which is also what an idle command bus reads as. Decoding cmd without qualifying it makes "the host sent nothing" and "the host sent NONE" the same event — harmless here only because CMD_NONE does nothing, and a latent bug the moment the encoding changes.

8. SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package usb_port_pkg;
  // The six port states that carry distinct behaviour. USB 2.0 section 11.5
  // names more; these are the ones a repeater-facing FSM must distinguish.
  typedef enum logic [2:0] {
    P_POWERED_OFF,   // no power on the port; nothing can be sensed
    P_DISCONNECTED,  // powered, nothing attached
    P_DISABLED,      // attached, not passing traffic
    P_RESETTING,     // driving reset downstream
    P_ENABLED,       // the ONLY state that passes traffic
    P_SUSPENDED      // enabled but idled
  } port_state_e;

  // Port commands, as the hub controller decodes them from the host's
  // SetPortFeature / ClearPortFeature control transfers.
  typedef enum logic [2:0] {
    CMD_NONE,
    CMD_SET_POWER,
    CMD_CLR_POWER,
    CMD_SET_RESET,
    CMD_SET_SUSPEND,
    CMD_CLR_SUSPEND,   // resume
    CMD_CLR_ENABLE     // disable, by request
  } port_cmd_e;

  // Change-bit positions, shared with chapter 18.3's status-change endpoint.
  typedef enum int unsigned {
    CHG_CONNECT = 0, CHG_ENABLE = 1, CHG_SUSPEND = 2,
    CHG_RESET   = 3, CHG_OC     = 4
  } port_chg_e;
endpackage

// usb_port_fsm_sv -- the per-port state machine inside a hub's controller.
//
// Chapter 18.1 built the repeater and left `port_enabled` as an INPUT. This
// drives it: one instance per downstream port.
//
// Three UNCOORDINATED sources move a port and can act in the same cycle: the
// HOST issuing port commands, the DEVICE attaching and detaching, and the
// HUB ITSELF disabling a port for cause (18.1's babble, and over-current).
// The host is the slowest of the three and the only one that can be wrong
// about the present, because its command was composed from a status it read
// some time ago. So the precedence is NOT "host wins":
//
//      detach  >  over-current  >  babble  >  host command
//
// And: THERE IS NO ENABLE COMMAND. A port becomes Enabled only by completing
// a reset, or by resuming from Suspended.
module usb_port_fsm_sv
  import usb_port_pkg::*;
#(
  parameter int unsigned RESET_CYCLES = 20
) (
  input  logic        clk,
  input  logic        rst_n,
  input  logic        cmd_valid,
  input  port_cmd_e   cmd,
  input  logic        attached,
  input  logic        babble,
  input  logic        overcurrent,

  output port_state_e state,
  output logic        port_powered,
  output logic        port_enabled,
  output logic        resetting,

  input  logic        chg_clear,
  input  logic [4:0]  chg_clear_mask,
  output logic [4:0]  change
);
  initial if (RESET_CYCLES < 1)
    $fatal(1, "RESET_CYCLES=%0d: a reset with no duration is not a reset",
           RESET_CYCLES);

  assign port_powered = (state != P_POWERED_OFF);
  // ONLY Enabled passes traffic. Suspended does not; a port being reset
  // emphatically does not.
  assign port_enabled = (state == P_ENABLED);
  assign resetting    = (state == P_RESETTING);

  int unsigned rst_cnt;
  logic        attached_q;

  // A detach is only observable while powered: with no power there is no
  // pull-up to sense, so `attached` means nothing.
  wire detached     = port_powered && !attached;
  // Connect-change is an EDGE, not a level. Driving it from `detached`
  // would re-set it every cycle an empty port sat idle, and a change bit
  // the host can never clear reports nothing.
  wire conn_changed = port_powered && (attached != attached_q);

  logic [4:0] set_chg;
  always_comb begin
    set_chg = '0;
    if (state != P_POWERED_OFF) begin
      if (overcurrent)  set_chg[CHG_OC]      = 1'b1;
      if (conn_changed) set_chg[CHG_CONNECT] = 1'b1;
      if (state == P_ENABLED && babble && !overcurrent && !detached)
        set_chg[CHG_ENABLE] = 1'b1;
      if (state == P_RESETTING && rst_cnt == RESET_CYCLES-1
          && !detached && !overcurrent)
        set_chg[CHG_RESET] = 1'b1;
      if (state == P_SUSPENDED && cmd_valid && cmd == CMD_CLR_SUSPEND
          && !detached && !overcurrent)
        set_chg[CHG_SUSPEND] = 1'b1;
    end
  end

  wire is_pwr_off = cmd_valid && (cmd == CMD_CLR_POWER);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state <= P_POWERED_OFF; rst_cnt <= '0; change <= '0; attached_q <= 1'b0;
    end else begin
      attached_q <= (state != P_POWERED_OFF) ? attached : 1'b0;

      // SET beats CLEAR, always (chapter 18.3). An event arriving in the
      // same cycle the host clears the bit must survive: this ordering
      // loses an acknowledgement, which the host sees on its next poll,
      // rather than losing an event, which leaves no trace at all.
      change <= (change & ~(chg_clear ? chg_clear_mask : 5'd0)) | set_chg;

      if (detached) begin
        // Physical reality first, whatever the host just asked for.
        state <= P_DISCONNECTED; rst_cnt <= '0;
      end else if (overcurrent && state != P_POWERED_OFF) begin
        state <= P_POWERED_OFF; rst_cnt <= '0;
      end else if (is_pwr_off && state != P_POWERED_OFF) begin
        state <= P_POWERED_OFF; rst_cnt <= '0;
      end else begin
        case (state)
          P_POWERED_OFF:
            if (cmd_valid && cmd == CMD_SET_POWER) state <= P_DISCONNECTED;
          P_DISCONNECTED:
            if (attached) state <= P_DISABLED;
          P_DISABLED:
            // No command moves a port from Disabled to Enabled: the only
            // route is through a reset.
            if (cmd_valid && cmd == CMD_SET_RESET) begin
              state <= P_RESETTING; rst_cnt <= '0;
            end
          P_RESETTING:
            if (rst_cnt == RESET_CYCLES-1) begin
              state <= P_ENABLED; rst_cnt <= '0;   // THIS is where a port
            end else begin                          // becomes enabled
              rst_cnt <= rst_cnt + 1;
            end
          P_ENABLED:
            // BABBLE OUTRANKS THE HOST'S COMMAND: the host composed that
            // command from a status read taken before the fault existed.
            if (babble)                                    state <= P_DISABLED;
            else if (cmd_valid && cmd == CMD_CLR_ENABLE)   state <= P_DISABLED;
            else if (cmd_valid && cmd == CMD_SET_SUSPEND)  state <= P_SUSPENDED;
          P_SUSPENDED:
            if (cmd_valid && cmd == CMD_CLR_SUSPEND)       state <= P_ENABLED;
            else if (cmd_valid && cmd == CMD_CLR_ENABLE)   state <= P_DISABLED;
          default: state <= P_POWERED_OFF;
        endcase
      end
    end
  end
endmodule

Hoisting the power-off command out of the case statement is the structural difference from the Verilog. Both designs behave identically; the SystemVerilog states the §3 exception once, next to the other two precedence rules, instead of repeating if (is_pwr_off) in five branches. Five copies of a rule is five chances to omit one, and the omission would be invisible in any test that never issues a power-off from the state that forgot it.

9. VHDL-2008

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

package usb_port_pkg is
  -- The six port states that carry distinct behaviour. USB 2.0 section 11.5
  -- names more; these are the ones a repeater-facing FSM must distinguish.
  type port_state_t is (
    P_POWERED_OFF,   -- no power on the port; nothing can be sensed
    P_DISCONNECTED,  -- powered, nothing attached
    P_DISABLED,      -- attached, not passing traffic
    P_RESETTING,     -- driving reset downstream
    P_ENABLED,       -- the ONLY state that passes traffic
    P_SUSPENDED      -- enabled but idled
  );

  -- Port commands, as the hub controller decodes them from the host's
  -- SetPortFeature / ClearPortFeature control transfers.
  type port_cmd_t is (
    CMD_NONE, CMD_SET_POWER, CMD_CLR_POWER, CMD_SET_RESET,
    CMD_SET_SUSPEND, CMD_CLR_SUSPEND, CMD_CLR_ENABLE
  );

  -- Change-bit positions, shared with chapter 18.3's status-change endpoint.
  constant CHG_CONNECT : natural := 0;
  constant CHG_ENABLE  : natural := 1;
  constant CHG_SUSPEND : natural := 2;
  constant CHG_RESET   : natural := 3;
  constant CHG_OC      : natural := 4;
end package;

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

-- usb_port_fsm_vhdl -- the per-port state machine inside a hub's controller.
--
-- Chapter 18.1 built the repeater and left `port_enabled` as an INPUT. This
-- drives it: one instance per downstream port.
--
-- Three UNCOORDINATED sources move a port and can act in the same cycle: the
-- HOST issuing port commands, the DEVICE attaching and detaching, and the
-- HUB ITSELF disabling a port for cause (18.1's babble, and over-current).
-- The host is the slowest of the three and the only one that can be wrong
-- about the present, because its command was composed from a status it read
-- some time ago. So the precedence is NOT "host wins":
--
--      detach  >  over-current  >  babble  >  host command
--
-- And: THERE IS NO ENABLE COMMAND. A port becomes Enabled only by completing
-- a reset, or by resuming from Suspended.
entity usb_port_fsm_vhdl is
  generic ( RESET_CYCLES : positive := 20 );
  port (
    clk            : in  std_logic;
    rst_n          : in  std_logic;
    cmd_valid      : in  std_logic;
    cmd            : in  port_cmd_t;
    attached       : in  std_logic;
    babble         : in  std_logic;
    overcurrent    : in  std_logic;

    state          : out port_state_t;
    port_powered   : out std_logic;
    port_enabled   : out std_logic;
    resetting      : out std_logic;

    chg_clear      : in  std_logic;
    chg_clear_mask : in  std_logic_vector(4 downto 0);
    change         : out std_logic_vector(4 downto 0)
  );
end entity;

architecture rtl of usb_port_fsm_vhdl is
  signal st         : port_state_t := P_POWERED_OFF;
  signal rst_cnt    : natural range 0 to RESET_CYCLES := 0;
  signal attached_q : std_logic := '0';
  signal chg_r      : std_logic_vector(4 downto 0) := (others => '0');

  signal powered_i  : std_logic;
  signal detached   : std_logic;
  signal conn_chg   : std_logic;
  signal set_chg    : std_logic_vector(4 downto 0);
begin
  powered_i    <= '0' when st = P_POWERED_OFF else '1';
  port_powered <= powered_i;
  -- ONLY Enabled passes traffic. Suspended does not; a port being reset
  -- emphatically does not.
  port_enabled <= '1' when st = P_ENABLED   else '0';
  resetting    <= '1' when st = P_RESETTING else '0';
  state        <= st;
  change       <= chg_r;

  -- A detach is only observable while powered: with no power there is no
  -- pull-up to sense, so `attached` means nothing.
  detached <= '1' when (powered_i = '1' and attached = '0') else '0';
  -- Connect-change is an EDGE, not a level. Driving it from `detached`
  -- would re-set it every cycle an empty port sat idle, and a change bit
  -- the host can never clear reports nothing.
  conn_chg <= '1' when (powered_i = '1' and attached /= attached_q) else '0';

  process (st, rst_cnt, attached, babble, overcurrent, cmd, cmd_valid,
           detached, conn_chg)
    variable r : std_logic_vector(4 downto 0);
  begin
    r := (others => '0');
    if st /= P_POWERED_OFF then
      if overcurrent = '1' then r(CHG_OC)      := '1'; end if;
      if conn_chg    = '1' then r(CHG_CONNECT) := '1'; end if;
      if st = P_ENABLED and babble = '1' and overcurrent = '0'
         and detached = '0' then
        r(CHG_ENABLE) := '1';
      end if;
      if st = P_RESETTING and rst_cnt = RESET_CYCLES-1
         and detached = '0' and overcurrent = '0' then
        r(CHG_RESET) := '1';
      end if;
      if st = P_SUSPENDED and cmd_valid = '1' and cmd = CMD_CLR_SUSPEND
         and detached = '0' and overcurrent = '0' then
        r(CHG_SUSPEND) := '1';
      end if;
    end if;
    set_chg <= r;
  end process;

  process (clk, rst_n)
    variable clr : std_logic_vector(4 downto 0);
  begin
    if rst_n = '0' then
      st <= P_POWERED_OFF; rst_cnt <= 0; chg_r <= (others => '0');
      attached_q <= '0';
    elsif rising_edge(clk) then
      if st /= P_POWERED_OFF then attached_q <= attached;
      else                        attached_q <= '0'; end if;

      -- SET beats CLEAR, always (chapter 18.3). An event arriving in the
      -- same cycle the host clears the bit must survive: this ordering
      -- loses an acknowledgement, which the host sees on its next poll,
      -- rather than losing an event, which leaves no trace at all.
      if chg_clear = '1' then clr := chg_clear_mask;
      else                    clr := (others => '0'); end if;
      chg_r <= (chg_r and (not clr)) or set_chg;

      if detached = '1' then
        -- Physical reality first, whatever the host just asked for.
        st <= P_DISCONNECTED; rst_cnt <= 0;
      elsif overcurrent = '1' and st /= P_POWERED_OFF then
        st <= P_POWERED_OFF; rst_cnt <= 0;
      elsif cmd_valid = '1' and cmd = CMD_CLR_POWER and st /= P_POWERED_OFF then
        st <= P_POWERED_OFF; rst_cnt <= 0;
      else
        case st is
          when P_POWERED_OFF =>
            if cmd_valid = '1' and cmd = CMD_SET_POWER then
              st <= P_DISCONNECTED;
            end if;
          when P_DISCONNECTED =>
            if attached = '1' then st <= P_DISABLED; end if;
          when P_DISABLED =>
            -- No command moves a port from Disabled to Enabled: the only
            -- route is through a reset.
            if cmd_valid = '1' and cmd = CMD_SET_RESET then
              st <= P_RESETTING; rst_cnt <= 0;
            end if;
          when P_RESETTING =>
            if rst_cnt = RESET_CYCLES-1 then
              st <= P_ENABLED; rst_cnt <= 0;   -- THIS is where a port
            else                                -- becomes enabled
              rst_cnt <= rst_cnt + 1;
            end if;
          when P_ENABLED =>
            -- BABBLE OUTRANKS THE HOST'S COMMAND: the host composed that
            -- command from a status read taken before the fault existed.
            if babble = '1' then
              st <= P_DISABLED;
            elsif cmd_valid = '1' and cmd = CMD_CLR_ENABLE then
              st <= P_DISABLED;
            elsif cmd_valid = '1' and cmd = CMD_SET_SUSPEND then
              st <= P_SUSPENDED;
            end if;
          when P_SUSPENDED =>
            if cmd_valid = '1' and cmd = CMD_CLR_SUSPEND then
              st <= P_ENABLED;
            elsif cmd_valid = '1' and cmd = CMD_CLR_ENABLE then
              st <= P_DISABLED;
            end if;
        end case;
      end if;
    end if;
  end process;
end architecture;

case st is over an enumerated type needs no default, and VHDL will refuse to compile if a state is left unhandled. The Verilog needs default: state <= P_POWERED_OFF; because [2:0] has eight values and only six are named — two encodings exist that the state table does not describe, and a design without that default would latch on them.

rst_cnt : natural range 0 to RESET_CYCLES makes the counter's bound part of its type. An overflow is a runtime error naming the signal, not a silent wrap.

10. The Waveform

A port from powered-off to enabled, then disabled by a babble that outranked the host

10 cycles
A waveform of a hub downstream port state machine over thirteen controller clocks, with the reset duration parameter reduced to three cycles so the entire life of a port fits in one figure. At cycle zero the port is in the Powered-off state and port enabled is low. At cycle one the host issues a SetPortFeature POWER command, and by cycle two the port has moved to Disconnected: it is powered but empty. At cycle three a device attaches, and by cycle four the port has moved to Disabled and the connect change bit has been set to record the attachment. At cycle five the host issues a SetPortFeature RESET command, and the port enters the Resetting state for cycles six, seven and eight, during which port enabled remains low, because a port being reset does not carry traffic. At cycle nine the reset duration elapses, the port becomes Enabled, port enabled asserts for the first time, and the reset change bit is set alongside the connect change bit that is still waiting to be acknowledged. At cycle ten the host issues a SetPortFeature SUSPEND command in the very same cycle that the repeater reports a babble fault on this port. The babble wins: at cycle eleven the port is in the Disabled state rather than Suspended, port enabled has fallen to low, and the enable change bit has been set to record that the port left the Enabled state for a reason the host did not ask for. The port remains Disabled at cycle twelve.attach → connect changeattach → connect changeResetting — NOT enabledResetting — NOT enabledreset complete → ENABLEDreset complete → ENABLEDbabble + SUSPEND in one cyclebabble + SUSPEND in onecycleclkstateOFFDISCDISCDISDISRSTRSTENENDIScmd_validcmdPOWER———RESET———SUSP—attachedbabbleport_enabledchange00000000000000000001000010000100001010010100101011t0t1t2t3t4t5t6t7t8t9
Figure 2 — ten controller clocks taken from the simulator, with RESET_CYCLES reduced to 2 so the whole life of a port fits in one figure. The port is powered, a device attaches, the host resets it, and it becomes enabled. At cycle 8 a babble fault and a host SUSPEND command arrive together; the port goes to Disabled, not Suspended.

This waveform is in the controller clock domain. Nothing here is a shape on D+/D−; cmd is a decoded control transfer, not a packet.

Cycles 5 and 6 are the point most often got wrong. The port is Resetting and port_enabled is low throughout. A design that enabled the port at the start of the reset would pass any test that only checks the port ends up enabled.

Cycle 8 is the chapter. Two things arrive in one cycle and the design's answer is visible one cycle later: state is DIS, not SUSP, and change has gained bit 1 — the port left Enabled for a reason the host did not ask for, so the host is told.

11. The Testbench: 784 Transitions, Exhaustively

The FSM's one-step domain is small enough to enumerate, so — following 18.1 §12 and 17.3 — it is enumerated.

7 positions × 14 command scenarios × 8 environment combinations = 784 transitions.

The seven positions are the six states plus a split of Resetting into early (the counter has just started) and final (the counter is about to expire), because those two behave differently and a single "Resetting" position would test only one of them. The fourteen command scenarios are cmd_valid crossed with all seven commands — which is what proves cmd_valid actually qualifies the decode. The eight environment combinations are attached, babble and overcurrent.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    for (pos=0; pos<7; pos=pos+1)
     for (cs=0; cs<14; cs=cs+1)
      for (st=0; st<8; st=st+1) begin
        goto(POSN[pos]);
        n_exh = n_exh + 1;
        step(cs[0], cs[3:1], st[0], st[1], st[2], 0, 5'd0);
      end

goto re-runs a hard reset before every point, so the 784 transitions are independent of each other and of their order.

The model keeps its own state, per 17.3's lesson, and it is written as the precedence chain of §3 rather than as a copy of the RTL's case statement:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  function [2:0] m_next(input [2:0] s, input [31:0] cnt, input cv,
                        input [2:0] c, input att, input bab, input oc);
    reg det;
    begin
      det = (s != P_OFF) && !att;
      if (det)                        m_next = P_DISC;
      else if (oc && (s != P_OFF))    m_next = P_OFF;
      else if (cv && c==C_PWR_OFF && s != P_OFF) m_next = P_OFF;
      else case (s)
        ...

Three invariants are checked on every one of the ~26 000 transitions, and none of them comes from the model:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      check(penabled === (state == P_EN),
            "port_enabled is asserted in the Enabled state and nowhere else");
      ...
      // A port may become Enabled only by COMPLETING A RESET or by RESUMING
      // from Suspended. There is no enable command, so a port must never
      // reach Enabled straight from Disabled, Disconnected or Powered-off.
      if (state == P_EN && prev_state != P_EN) begin
        n_enter_en = n_enter_en + 1;
        check(prev_state === P_RST || prev_state === P_SUSP,
              "a port entered Enabled other than by reset-complete or resume");
      end
      // A change bit never clears unless the host asked for it.
      check(((~change) & m_chg & ~(clr ? clrm : 5'd0)) === 5'd0,
            "a change bit cleared without the host clearing it");

Measured reach, all three languages:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  exhaustive transition sweep: 784 of 784 transitions verified

  Verilog / SystemVerilog:
  REACH: exhaustive=784 enabled-entries=1396 (reset=1088 resume=308) babble+cmd collisions=378
  [Verilog] usb_port_fsm: 0 errors — PASS

  VHDL:
  REACH: exhaustive=784 enabled-entries=1398 (reset=1091 resume=307) babble+cmd collisions=378
  [VHDL] usb_port_fsm_vhdl: 0 errors — PASS

12. The Invariant That Was Wrong

The history invariant in §11 did not start out in that form. It started as the rule §2 states:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // A port may become Enabled ONLY by completing a reset.
      check(prev_state === P_RST, "a port entered Enabled from somewhere else");

It reported 8 failures against a design that was correct.

All eight were resume. A port in Suspended, given ClearPortFeature(SUSPEND), returns to Enabled — which is exactly what resume is for, and involves no reset at all.

"There is no enable command" forbids Disabled → Enabled. It says nothing about a port coming back from suspend. The invariant had taken a true statement and dropped a qualifier, and the bench dutifully reported the design breaking a rule that was never a rule.

A failing check is a disagreement between the design and the testbench. Which of the two is wrong is a separate question, and assuming it is always the design is how correct behaviour gets "fixed" out of a design.

The count is what made this obvious. Eight failures — not zero, not thousands. A design that could reach Enabled by an illegal route would do it constantly; eight occurrences, all with the same predecessor, describes a specific legal transition the invariant had not allowed for.

The corrected invariant is strictly more useful than the original, because it now distinguishes the two legal routes and counts them separately: reset=1088 resume=308. That split is how §13's stimulus gap was noticed.

13. Mutation Testing — Across All Three Languages

Seven mutations, each a plausible alternative design, run against all three benches.

MutationVerilogSystemVerilogVHDL
M1the host's command outranks babble103103103
M2a host command outranks a detach436843684429
M3on the change register, clear beats set192519251944
M4a command enables a port directly, no reset131713171676
M5the reset finishes one cycle early134001340013958
M6a suspended port still passes traffic879879868
M7connect-change is a level, not an edge220092200922341

M7 scores highest because its failure is continuous. A level-driven connect-change re-sets itself every cycle an empty port sits idle, so it diverges from the model on essentially every transition where a port is not populated.

M4 is §2's rule as a mutation — a command that enables a port with no reset. It dies 1317 times, but the check that specifically catches it is §11's history invariant, which is also the check §12 had to correct before it was usable.

M6 is the Resetting/Suspended distinction. A suspended port that still asserts port_enabled would let 18.1's repeater broadcast host traffic to a device that has been told to idle.

14. M1 Died by Three

M1 is the mutation this chapter exists for — it makes the host's command outrank babble, which is §3 inverted. In the first run it scored 3.

Three errors, in all three languages, for the chapter's central claim.

The reason is that M1's distinguishing condition is far narrower than it looks. M1 and the correct design differ only when all four of these hold:

  • the port is in Enabled, and
  • babble is asserted, and
  • cmd_valid is asserted, and
  • the command is specifically SET_SUSPEND.

CLR_ENABLE does not distinguish them — both designs go to Disabled, by different routes, and agree. Every other command is either outranked anyway or irrelevant. So the whole mutation hangs on one command in one state with one fault present.

In the 784-point sweep that is exactly one point: one position out of seven, one command scenario out of fourteen, one environment combination out of eight. The directed test added a second. The randomised phase supplied the third.

The fix is a directed phase that collides babble with every command, fifty times each:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    // This is the behaviour the chapter is about, and the sweep reaches it
    // exactly once: it needs Enabled, babble, cmd_valid, and specifically
    // CMD_SET_SUSPEND -- one point in 784. A claim this central cannot rest
    // on a single transition.
    for (pos=0; pos<50; pos=pos+1)
      for (i=0;i<7;i=i+1) begin
        goto(P_EN);
        step(1, i[2:0], 1, 1, 0, 0, 5'd0);
        // CLR_POWER is not a COMPETING command, it is a stronger one:
        // removing power takes the port past Disabled altogether. Babble
        // outranks every command that would leave the port powered.
        if (i == C_PWR_OFF)
          check(state===P_OFF, "a power-off command outranks babble");
        else
          check(state===P_DIS,
                "babble outranks every host command that leaves it powered");
      end

That phase immediately reported 50 failures of its own — one per repetition, all on CLR_POWER. The check as first written said "babble outranks every host command", and power-off is the exception §3 describes: it is not competing with babble, it is doing more than babble. The testbench had over-stated the rule in exactly the way §12's invariant had.

With the exception written in: M1 went from 3 to 103, and babble+cmd collisions from 28 to 378.

15. The Random Stimulus That Could Not Clear Three Bits

The first matrix had a second problem, visible only as a cross-language disagreement: M3 scored 1925 in Verilog and SystemVerilog but 1190 in VHDL, and M5 scored 13 400 against 28 676.

The cause was in the VHDL bench's randomisation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        step(sl(r2 < 0.3333), CMDS(integer(floor(r3*7.0))),
             sl(r4 < 0.75), sl(r5 < 0.0625), sl(r6 < 0.015625),
             sl(r7 < 0.125),                                   -- clear?
             std_logic_vector(to_unsigned(integer(floor(r7*32.0)), 5)));

r7 gates the clear and generates the mask. So a clear only ever happened when r7 < 0.125, which forces floor(r7*32) < 4 — the mask was never more than 3, and change bits 2, 3 and 4 were never cleared at all in twenty thousand cycles.

M3 is a mutation of the clear/set ordering. A bench that only ever clears two of five bits exercises it at a fraction of its strength.

One extra random draw fixed it, and the effect was not confined to M3:

MutationVHDL beforeVHDL afterVerilog / SV
M2618844294368
M3119019441925
M5286761395813400

All three converged on the other two languages at once, which is the strongest available evidence that the divergence was the stimulus and not the designs.

Two independent random values must come from two independent draws. Reusing one for a gate and a payload correlates them perfectly, and the resulting hole is invisible in a passing run — a green testbench that simply never asked some of its questions.

18.1 §15 used a cross-language disagreement to find a mis-written mutation. This one used the same signal to find a mis-written testbench. Neither would have been visible from a single language.

16. A UVM Environment for the Port FSM

The three sources of §3 map naturally onto three independent agents driving one DUT, which is what makes this block a better UVM example than 18.1's repeater: the agents are not symmetric copies, they are genuinely different actors.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// One agent per SOURCE, not per signal. The whole design problem is that
// these three are uncoordinated, so the environment must be able to make
// them collide without any of them knowing about the others.
class port_env extends uvm_env;
  `uvm_component_utils(port_env)
  host_cmd_agent   host;    // SetPortFeature / ClearPortFeature
  device_agent     device;  // attach / detach
  fault_agent      fault;   // babble (from 18.1) and over-current
  port_scoreboard  sb;
  port_coverage    cov;
endclass

The collision is a virtual sequence, because it is a statement about several agents at once:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class babble_vs_suspend_vseq extends uvm_sequence;
  `uvm_object_utils(babble_vs_suspend_vseq)
  port_vsequencer vsqr;

  task body();
    host_cmd_item   h;
    fault_item      f;
    // The SAME cycle. Section 14 measured what happens when this scenario
    // is left to chance: the mutation that inverts this precedence died by
    // THREE errors in a 26000-transition run.
    fork
      begin h = host_cmd_item::type_id::create("h");
            start_item(h, null, vsqr.host_sqr);
            h.cmd = CMD_SET_SUSPEND; h.valid = 1;
            finish_item(h, null); end
      begin f = fault_item::type_id::create("f");
            start_item(f, null, vsqr.fault_sqr);
            f.babble = 1;
            finish_item(f, null); end
    join
  endtask
endclass

The scoreboard checks the history invariant, which is the part no reference model supplies:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class port_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(port_scoreboard)
  local port_state_e m_state, m_prev;   // its OWN state (chapter 17.3)

  function void write(port_txn t);
    // A port may become Enabled only by completing a reset or by resuming.
    // The first version of this check named P_RESETTING alone and reported
    // 8 failures against a correct design -- every one of them a legal
    // resume. See section 12.
    if (t.state == P_ENABLED && m_prev != P_ENABLED)
      if (!(m_prev inside {P_RESETTING, P_SUSPENDED}))
        `uvm_error("PORT/ENTRY", $sformatf(
          "entered Enabled from %s; only reset-complete and resume are legal",
          m_prev.name()))

    // Exactly one state carries traffic.
    if (t.port_enabled !== (t.state == P_ENABLED))
      `uvm_error("PORT/EN", "port_enabled asserted outside the Enabled state")

    m_prev = m_state; m_state = t.state;
  endfunction
endclass

And the coverage model encodes §14's lesson directly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup port_cg with function sample(
    port_state_e s, port_cmd_e c, bit cv, bit bab, bit oc, bit att);
  cp_state : coverpoint s;
  cp_cmd   : coverpoint c;
  cp_bab   : coverpoint bab { bins fault = {1}; }

  // THE cross that matters. Section 14: the one-step sweep reaches
  // Enabled x babble x SET_SUSPEND exactly ONCE in 784 transitions, and a
  // mutation inverting that precedence survived on a margin of three.
  // This cross makes the gap a coverage hole instead of a lucky catch.
  x_babble_vs_cmd : cross cp_state, cp_cmd, cp_bab {
    ignore_bins not_enabled = binsof(cp_state) intersect
      {P_POWERED_OFF, P_DISCONNECTED, P_DISABLED, P_RESETTING, P_SUSPENDED};
  }
endgroup

17. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // Exactly one state carries traffic.
  property p_enabled_only_in_enabled;
    @(posedge clk) disable iff (!rst_n)
      port_enabled == (state == P_ENABLED);
  endproperty
  a_enabled_only : assert property (p_enabled_only_in_enabled);

  // THE precedence: babble, with the port powered and staying powered,
  // always lands the port in Disabled -- whatever the host asked for.
  property p_babble_outranks_host;
    @(posedge clk) disable iff (!rst_n)
      (state == P_ENABLED && babble && !overcurrent && attached
       && !(cmd_valid && cmd == CMD_CLR_POWER)) |=> (state == P_DISABLED);
  endproperty
  a_babble_wins : assert property (p_babble_outranks_host)
    else $error("a host command overrode a babble fault");

  // There is no enable command: Enabled is reachable only from Resetting
  // or Suspended. Section 12 is why the second term is here.
  property p_enable_entry;
    @(posedge clk) disable iff (!rst_n)
      (state == P_ENABLED && $past(state) != P_ENABLED)
        |-> ($past(state) == P_RESETTING || $past(state) == P_SUSPENDED);
  endproperty
  a_enable_entry : assert property (p_enable_entry);

  // A change bit never clears unless the host cleared it.
  property p_change_sticky;
    @(posedge clk) disable iff (!rst_n)
      ($past(change) & ~change & ~($past(chg_clear) ? $past(chg_clear_mask) : '0))
        == '0;
  endproperty
  a_change_sticky : assert property (p_change_sticky);

  // A detach outranks everything except nothing: the port always lands in
  // Disconnected, whatever else arrived in the same cycle.
  property p_detach_wins;
    @(posedge clk) disable iff (!rst_n)
      (state != P_POWERED_OFF && !attached) |=> (state == P_DISCONNECTED);
  endproperty
  a_detach_wins : assert property (p_detach_wins);

p_babble_outranks_host is M1 as a single line, and it carries the power-off exception §14 discovered — the same qualifier, in the same place, for the same reason.

These were written but not simulated. Icarus does not support concurrent assertions; §21 says so plainly.

18. Debugging: the Device That Enumerates, Then Vanishes

The report: a device attached to a hub port enumerates correctly, works for a few seconds, then disappears. The host logs a disconnect. Re-plugging it works, for a few seconds. The device works perfectly on a direct host port.

The procedure:

1. Read the port state. If it is Disabled rather than Disconnected, the device did not detach — the hub disabled it. The host's "disconnect" is what a disabled port looks like from above.

2. Read the change bits. Enable-change set with no matching host command means the port left Enabled for a reason the host did not ask for — §5's whole purpose. That is 18.1's babble path.

3. Check port_isolated on the repeater (18.1 §18). If two bits are set, the fault involved two ports, and this device may be the innocent one.

4. Distinguish over-current from babble. Over-current lands the port in Powered-off, not Disabled, and sets a different change bit. Two different faults with two different destinations — which is why §5 keeps five separate bits rather than one "something happened" flag.

5. Explain "works on a direct host port." A root port has no repeater in front of it, so there is nothing to detect a multi-talker condition and nothing to disable the port. The device may be misbehaving in exactly the way the hub is built to catch, and the direct connection simply has no detector.

19. Common Misconceptions

"The host enables a port." There is no enable command (§2). The host requests a reset and the enable follows from its completion.

"A port is enabled while it is being reset." port_enabled is low throughout Resetting (§1, §10) — mutation M6's neighbour, and visible at cycles 6–8 of Figure 2.

"The host's command always wins." It is outranked by a detach, by over-current, and by babble (§3). The host is the only actor that can be wrong about the present.

"Babble outranks every host command." Every command that leaves the port powered (§3). ClearPortFeature(PORT_POWER) does more than babble does, not less — a distinction that cost 50 test failures (§14).

"Change bits should clear when the condition ends." Then the host, which polls, would miss every event shorter than its polling interval (§5).

"If the host clears a bit as an event sets it, the clear should win." Then the event is lost with no trace. Set wins; an acknowledgement is recoverable, an event is not (§5).

"Connect-change means a device is connected." It means the connection status changed (§5). Driven as a level it can never be cleared — 22 009 errors.

"A disabled port means a broken device." It may be the innocent half of a two-port fault (§18, 18.1 §3).

"All 784 transitions passed, so the FSM is verified." The single most important transition among them contributed one error, and the mutation testing that behaviour died by three (§14).

20. Exercises

1. §14 found that CLR_ENABLE does not distinguish M1 from the correct design, because both reach Disabled. Identify every other mutation in §13 that has a similarly narrow distinguishing condition, and compute how many of the 784 sweep points reach each.

2. The design resets attached_q to 0 while the port is unpowered. Determine what changes if it holds its value instead, and which change bit misbehaves.

3. Write the SVA property that catches M5 — a reset finishing one cycle early — without referring to RESET_CYCLES.

4. §15's correlated random draw made three mutations diverge at once. Describe a check over a tri-HDL mutation matrix that flags this class of problem automatically, and state its false-positive rate against the matrix in §13.

5. Extend the FSM with the USB Restart_S and Restart_E states from section 11.5 and determine where they sit in §3's precedence chain.

6. A hub has 4 ports and one instance of this FSM each. Chapter 18.3 must report all of their change bits in one bitmap. Determine what happens when two ports set a change bit in the same cycle the host clears the bitmap, and what §5's ordering rule becomes at the hub level.

21. Summary

A port is six states, and exactly one of them carries traffic (§1). port_enabled to 18.1's repeater is asserted in Enabled only — not in Resetting, not in Suspended.

There is no enable command (§2). A port becomes enabled by completing a reset, or by resuming from suspend, and by no other route — checked as a history invariant on every transition (§11).

Three uncoordinated sources move a port and collide in the same cycle (§3), and the host loses: detach > over-current > babble > host command. The host is the only one of the three whose information is stale by construction. The exception is ClearPortFeature(PORT_POWER), which is not a competing command but a stronger one (§3, §14).

Change bits are set by hardware and cleared only by the host (§5), connect-change is an edge rather than a level (M7: 22 009 errors), and set beats clear in the same cycle — losing an acknowledgement costs one poll, losing an event costs an enumeration.

All three HDL implementations were simulated (§22) and seven mutations died in all three (§13), with the transition function verified exhaustively over all 784 one-step transitions (§11).

The history invariant was wrong before the design was (§12). Stated as "only from Resetting" it reported 8 failures against correct hardware, every one a legal resume. A failing check is a disagreement; which side is wrong is a separate question.

M1 — the chapter's central claim — first died by 3 (§14). Its distinguishing case needs Enabled, babble, a valid command, and specifically SET_SUSPEND: one point in 784. An exhaustive sweep is exhaustive over its domain, not over the importance of what is in it. A directed collision phase took it to 103 — after that phase itself over-stated the rule and reported 50 failures of its own.

And a single reused random draw made the VHDL bench unable to clear three of five change bits (§15). Fixing it moved M2, M3 and M5 onto the other two languages simultaneously. Two independent values need two independent draws.

22. Tooling, Honestly

LanguageDesignTestbenchAnalysed / compiledSimulatedMutations
Verilog-2005usb_port_fsmpm_v_tb.v✅ Icarus -g2005✅ 0 errors, 784/784✅ all seven
SystemVerilogusb_port_fsm_svpm_sv_tb.sv✅ Icarus -g2012✅ 0 errors, 784/784✅ all seven
VHDL-2008usb_port_fsm_vhdlpm_vhdl_tb.vhd✅ nvc 1.23.0✅ 0 errors, 784/784✅ all seven
UVM (§16)——❌ no UVM-capable simulator here❌—
SVA (§17)——❌ unsupported by Icarus❌—

Icarus rejected two constructs that the published SystemVerilog would otherwise use: unique if on the precedence chain, and inside in the testbench. Both were rewritten rather than left in as untested code.

The UVM environment and assertions in §16 and §17 were written and reviewed, not simulated. No tool in this environment runs either.

23. What Comes Next

This chapter produced five change bits per port and gave the host no way to read them.

That is the next problem, and it is larger than it sounds. A hub cannot interrupt the host (Chapter 2.6), so every event on every port waits in a register until the host asks. The asking is an interrupt transfer from the hub's status-change endpoint — one bitmap covering all ports, polled at whatever interval the host chose.

Chapter 18.3 — Downstream Device Discovery builds that mechanism, and the collision §5 introduced per-port becomes a harder problem at hub level: the host reads a bitmap, decides which ports to investigate, and clears what it read — while ports it has not looked at yet are still setting bits. A device that attaches during that window must not be lost, and the ordering rule that guarantees it is the same one this chapter built into change.

Browse the full path on the USB tutorials index.

Continue learning

Standards & specifications

Governing standard
USB-IF (Universal Serial Bus Specification)(opens USB Implementers Forum (USB-IF) in a new tab)

Defines the USB bus — its electrical signalling, connectors, packet and transaction model, device framework and the descriptors a device must expose — together with the device-class specifications layered on it. It does not define host-controller register interfaces (xHCI and EHCI are separate documents) nor any operating system's driver architecture.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the USB curriculum.