USB · Module 27
The Enumeration Question
Attach to configured, with the one step almost everybody gets backwards — SET_ADDRESS takes effect after the status stage, and a device that switches early is invisible to the host.
Chapter 27.1 said a device may never initiate. Chapter 27.2 said a hub only reports when asked. Both leave the same question open, and it is the one interviewers use to separate people who have read the specification from people who have brought a board up.
1. The Question
"A device is plugged in. Walk me through what happens, end to end."
This is the most-asked intermediate USB question and it has a well-known answer, which is exactly why it is useful: everybody can produce a sequence, so the interviewer is not listening for the sequence. They are listening for one step — and for whether you know why it is the way it is.
2. The Sequence
Enumeration, with the address change in its correct place
Two things in that diagram are worth more than the rest.
Step 1 is polled. The hub detected the attach electrically and did nothing about it. It waits to be asked, because it is a device and devices do not initiate. The only information in USB that travels up the tree unbidden is an electrical condition, and even that is converted to a pollable status bit before anything in the data protocol can see it.
Step 4 happens at address 0 for a reason. The host does not yet know the control endpoint's maximum packet size, and it cannot read a descriptor properly without it. So the first read is deliberately small and deliberately at the default address, and its only purpose is to learn how to do the next one.
The address changes after the acknowledgement, not before
addressed stays low for the whole outstanding window too. That matters: a device that marks itself addressed when the request arrives will accept a SET_CONFIGURATION it has no business accepting, and section 13 shows what it costs to catch.
3. Why This Rule Exists
It looks like pedantry and it is not. Consider what the host has to do if the device switched early.
The host sends SET_ADDRESS(7) to address 0 and then needs to know whether the device got it. There is exactly one mechanism for that: the status stage, which the host performs at the address it sent the request to. If the device has already moved to 7, the status stage goes unanswered — and the host cannot tell "the device moved early" from "the device is not there."
Device switches EARLY:
host -> addr 0 : SET_ADDRESS(7)
host -> addr 0 : status stage device is at 7; silence
host : timeout
host -> addr 0 : SET_ADDRESS(7) retry; device is at 7; silence
host : "no device here"
Device switches AFTER the status stage:
host -> addr 0 : SET_ADDRESS(7)
host -> addr 0 : status stage ACK from address 0
device : now I am 7
host -> addr 7 : GET_DESCRIPTOR works4. The Second Lesson, Which Is Structural
There is a bug class in this design that has nothing to do with USB, and it cost more debugging time than the address rule did.
A first version kept the device's state and the outstanding request in one register, which is the obvious way to write an FSM. It is wrong here, because the two are not the same thing:
One register, `st`:
SETUP(SET_CONFIG) arrives -> st <= S_CONFIG_PEND
status stage for SET_ADDRESS-> st <= S_ADDRESSED
Both in the same cycle. Non-blocking, so the LATER write wins
and the other request is SILENTLY DROPPED.
Two registers:
pend_kind -- which request is outstanding
have_addr / conf_r -- what has actually COMPLETED
The status stage runs FIRST and retires pend_kind; the SETUP
decode runs SECOND and sets it. The new request survives, and
the completed one is recorded where nothing can overwrite it.So in the published design state is not a register at all — it is a decode of pend_kind, have_addr and conf_r. Nothing in the design branches on it, which means it cannot drift out of step with what the device actually does.
5. Seven Properties
| # | Property |
|---|---|
| 1 | The live address is the one the last completed SET_ADDRESS status stage established. |
| 2 | While a SET_ADDRESS is outstanding, the address has not moved. |
| 3 | addressed is false until a SET_ADDRESS status stage completes. |
| 4 | A bus reset returns the device to address 0, unaddressed and unconfigured, from any state. |
| 5 | SET_CONFIGURATION is refused unless a SET_ADDRESS has completed — gated on that, not on the address being non-zero. |
| 6 | A completed re-address unconfigures the device. |
| 7 | A status stage with nothing outstanding changes nothing. |
Property 5's parenthesis is the kind of detail that separates a working device from a nearly-working one: SET_ADDRESS(0) is legal and means "return to the default address". Gating configuration on addr != 0 therefore refuses to configure a device the host has deliberately returned to address 0, and — worse — the obvious self-check written that way produces thousands of false alarms. Section 13 has that story.
6. Verilog-2005 RTL
// =====================================================================
// usb_enum_fsm -- "Walk me through enumeration" answered in hardware.
//
// The sequence is easy to recite and has one step almost everybody gets
// backwards:
//
// SET_ADDRESS takes effect AFTER the status stage completes,
// not when the request arrives.
//
// The device must acknowledge the request at its OLD address -- which for
// a freshly attached device is address 0 -- and only then start answering
// at the new one. A device that switches early never sends the
// acknowledgement the host is waiting for: the host times out, retries at
// address 0, gets nothing (the device has moved), and gives up. The device
// works perfectly and is never seen.
//
// The second lesson is structural. The device STATE and the OUTSTANDING
// REQUEST are two different things, and a design that keeps them in one
// register silently drops a request whenever a new SETUP lands in the same
// cycle as a status stage for a different one. They are separate here.
// =====================================================================
module usb_enum_fsm (
input wire clk,
input wire rst_n,
// ---- the bus ----
input wire tok_valid,
input wire [1:0] tok_pid, // T_OUT / T_IN / T_SOF / T_SETUP
input wire [6:0] tok_addr,
// ---- a control transfer, decomposed into its three stages ----
input wire setup_valid, // an 8-byte SETUP packet arrived
input wire [7:0] req, // bRequest
input wire [15:0] val, // wValue
input wire data_stage, // a DATA stage packet moved
input wire status_ack, // the STATUS stage completed (ACK seen)
// ---- bus events outside the data protocol ----
input wire bus_reset,
// ---- what the device answers to, and where it is in its life ----
output wire [6:0] dev_addr,
output wire [2:0] state,
output wire configured,
output wire addressed,
// ---- observability ----
output wire [31:0] n_setup,
output wire [31:0] n_addr_change,
output wire [31:0] n_early_switch, // must always read 0
output wire [31:0] n_reset_to_default,
output wire [31:0] n_wrong_addr,
output wire [31:0] n_dropped_request
);
localparam [1:0] T_OUT = 2'd0, T_IN = 2'd1, T_SOF = 2'd2, T_SETUP = 2'd3;
localparam [7:0] R_GET_DESCRIPTOR = 8'h06,
R_SET_ADDRESS = 8'h05,
R_SET_CONFIG = 8'h09;
// ---- reported device state, from the specification ----
//
// ATTACHED and POWERED are electrical. A device's LOGIC begins at
// DEFAULT, which is the state in which it answers to address 0.
localparam [2:0] S_DEFAULT = 3'd0,
S_ADDR_PEND = 3'd1, // SET_ADDRESS seen, NOT applied
S_ADDRESSED = 3'd2,
S_CONFIG_PEND = 3'd3,
S_CONFIGURED = 3'd4;
// ---- the OUTSTANDING REQUEST, which is not the state ----
localparam [1:0] P_NONE = 2'd0, P_ADDR = 2'd1, P_CONF = 2'd2;
reg [6:0] addr_r;
reg [6:0] pend_addr;
reg [1:0] pend_kind;
// The live address as it stood when the request was recorded. The
// self-check below needs to know whether the address MOVED while a
// request was outstanding -- not whether it happens to equal the
// requested value, which it legitimately does for SET_ADDRESS(0).
reg [6:0] addr_at_req;
reg have_addr; // a SET_ADDRESS status stage has completed
reg conf_r;
reg [31:0] setup_c, chg_c, early_c, rst_c, wrong_c, drop_c;
// `state` is a DECODE of the real registers, not a register of its own.
// Nothing in the design branches on it, so it cannot go out of step with
// what the device actually does.
assign state = bus_reset ? S_DEFAULT
: (pend_kind == P_ADDR) ? S_ADDR_PEND
: (pend_kind == P_CONF) ? S_CONFIG_PEND
: conf_r ? S_CONFIGURED
: have_addr ? S_ADDRESSED
: S_DEFAULT;
assign dev_addr = addr_r;
assign configured = conf_r;
assign addressed = have_addr;
assign n_setup = setup_c;
assign n_addr_change = chg_c;
assign n_early_switch = early_c;
assign n_reset_to_default = rst_c;
assign n_wrong_addr = wrong_c;
assign n_dropped_request = drop_c;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
addr_r <= 7'd0;
pend_addr <= 7'd0;
pend_kind <= P_NONE;
addr_at_req <= 7'd0;
have_addr <= 1'b0;
conf_r <= 1'b0;
setup_c <= 32'd0;
chg_c <= 32'd0;
early_c <= 32'd0;
rst_c <= 32'd0;
wrong_c <= 32'd0;
drop_c <= 32'd0;
end else if (bus_reset) begin
// ---- a bus reset returns the device to DEFAULT ----
//
// Address 0, unaddressed, unconfigured, from any state, every time.
// This is why enumeration can always be restarted, and why a wedged
// device recovers on replug: the host resets the port first.
addr_r <= 7'd0;
pend_addr <= 7'd0;
pend_kind <= P_NONE;
addr_at_req <= 7'd0;
have_addr <= 1'b0;
conf_r <= 1'b0;
rst_c <= rst_c + 32'd1;
end else begin
// A token addressed to somebody else is not ours to act on.
if (tok_valid && (tok_addr != addr_r) && (tok_pid != T_SOF))
wrong_c <= wrong_c + 32'd1;
// =============================================================
// STATUS STAGE FIRST.
//
// It resolves whatever was outstanding BEFORE this cycle. Running
// it before the SETUP decode below means a new request arriving on
// the same edge overwrites `pend_kind` afterwards and is therefore
// kept -- rather than being cancelled by this block's write.
// =============================================================
if (status_ack) begin
pend_kind <= P_NONE;
if (pend_kind == P_ADDR) begin
// NOW. Not earlier. The acknowledgement has already been sent
// from the old address and the host has seen it.
addr_r <= pend_addr;
have_addr <= 1'b1;
// Re-addressing returns the device to the Address state, which
// is by definition unconfigured.
conf_r <= 1'b0;
chg_c <= chg_c + 32'd1;
end else if (pend_kind == P_CONF) begin
conf_r <= 1'b1;
end
end
// =============================================================
// SETUP DECODE SECOND, so the newest request wins.
// =============================================================
if (setup_valid) begin
setup_c <= setup_c + 32'd1;
// A SETUP that displaces a still-outstanding request is counted.
// It is legal -- the host may abandon a transfer at any time --
// but a design that does it by accident looks identical, so the
// number is published rather than assumed to be zero.
if ((pend_kind != P_NONE) && !status_ack)
drop_c <= drop_c + 32'd1;
case (req)
R_SET_ADDRESS: begin
// Recorded. NOT applied.
pend_addr <= val[6:0];
pend_kind <= P_ADDR;
// The address that will be LIVE after this edge -- which is not
// addr_r when a status stage is completing a previous
// SET_ADDRESS on this same edge.
addr_at_req <= (status_ack && (pend_kind == P_ADDR)) ? pend_addr
: addr_r;
end
R_SET_CONFIG: begin
// Only meaningful once a SET_ADDRESS has actually COMPLETED.
// Gated on have_addr rather than on the address being
// non-zero, because SET_ADDRESS(0) is legal and means
// "return to the default address".
if (have_addr) pend_kind <= P_CONF;
else pend_kind <= P_NONE;
end
R_GET_DESCRIPTOR: begin
// Legal in every state, including DEFAULT at address 0 -- it
// is how the host learns the packet size it must use for
// everything that follows. It leaves no state pending.
pend_kind <= P_NONE;
end
default: pend_kind <= P_NONE;
endcase
end
// ---- the self-check that makes the central rule measurable ----
//
// While a SET_ADDRESS is outstanding, the live address must not have
// MOVED from what it was when the request arrived. On a correct design
// this is unreachable; it is counted rather than asserted so that a run
// can publish the number zero.
//
// The first version of this check compared addr_r against pend_addr,
// which fires on the perfectly legal SET_ADDRESS(0) to a device that is
// already at address 0 -- 9607 false positives in one run. A checker
// that cries wolf is a checker somebody switches off.
if ((pend_kind == P_ADDR) && (addr_r != addr_at_req))
early_c <= early_c + 32'd1;
end
end
endmodule7. SystemVerilog RTL
// =====================================================================
// usb_enum_fsm -- SystemVerilog.
//
// The requests and the outstanding-request kind are named enumerations
// rather than magic numbers, which matters more here than usual: this
// design's central bug class is confusing WHICH request is outstanding
// with WHERE the device is in its life, and two enums with different
// types cannot be mixed up by accident.
//
// The sequence is easy to recite and has one step almost everybody gets
// backwards:
//
// SET_ADDRESS takes effect AFTER the status stage completes,
// not when the request arrives.
//
// The device must acknowledge the request at its OLD address -- which for
// a freshly attached device is address 0 -- and only then start answering
// at the new one. A device that switches early never sends the
// acknowledgement the host is waiting for: the host times out, retries at
// address 0, gets nothing (the device has moved), and gives up. The device
// works perfectly and is never seen.
//
// The second lesson is structural. The device STATE and the OUTSTANDING
// REQUEST are two different things, and a design that keeps them in one
// register silently drops a request whenever a new SETUP lands in the same
// cycle as a status stage for a different one. They are separate here.
// =====================================================================
module usb_enum_fsm (
input logic clk,
input logic rst_n,
// ---- the bus ----
input logic tok_valid,
input logic [1:0] tok_pid, // T_OUT / T_IN / T_SOF / T_SETUP
input logic [6:0] tok_addr,
// ---- a control transfer, decomposed into its three stages ----
input logic setup_valid, // an 8-byte SETUP packet arrived
input logic [7:0] req, // bRequest
input logic [15:0]val, // wValue
input logic data_stage, // a DATA stage packet moved
input logic status_ack, // the STATUS stage completed (ACK seen)
// ---- bus events outside the data protocol ----
input logic bus_reset,
// ---- what the device answers to, and where it is in its life ----
output logic [6:0] dev_addr,
output logic [2:0] state,
output logic configured,
output logic addressed,
// ---- observability ----
output logic [31:0]n_setup,
output logic [31:0]n_addr_change,
output logic [31:0]n_early_switch, // must always read 0
output logic [31:0]n_reset_to_default,
output logic [31:0]n_wrong_addr,
output logic [31:0]n_dropped_request
);
typedef enum logic [1:0] { T_OUT = 2'd0, T_IN = 2'd1,
T_SOF = 2'd2, T_SETUP = 2'd3 } tok_e;
localparam [7:0] R_GET_DESCRIPTOR = 8'h06,
R_SET_ADDRESS = 8'h05,
R_SET_CONFIG = 8'h09;
// ---- reported device state, from the specification ----
//
// ATTACHED and POWERED are electrical. A device's LOGIC begins at
// DEFAULT, which is the state in which it answers to address 0.
localparam [2:0] S_DEFAULT = 3'd0,
S_ADDR_PEND = 3'd1, // SET_ADDRESS seen, NOT applied
S_ADDRESSED = 3'd2,
S_CONFIG_PEND = 3'd3,
S_CONFIGURED = 3'd4;
// ---- the OUTSTANDING REQUEST, which is not the state ----
// A distinct TYPE from tok_e, so the compiler refuses to confuse the
// outstanding request with a token PID.
typedef enum logic [1:0] { P_NONE = 2'd0, P_ADDR = 2'd1,
P_CONF = 2'd2 } pend_e;
logic [6:0] addr_r;
logic [6:0] pend_addr;
pend_e pend_kind;
// The live address as it stood when the request was recorded. The
// self-check below needs to know whether the address MOVED while a
// request was outstanding -- not whether it happens to equal the
// requested value, which it legitimately does for SET_ADDRESS(0).
logic [6:0] addr_at_req;
logic have_addr; // a SET_ADDRESS status stage has completed
logic conf_r;
logic [31:0] setup_c, chg_c, early_c, rst_c, wrong_c, drop_c;
// `state` is a DECODE of the real registers, not a register of its own.
// Nothing in the design branches on it, so it cannot go out of step with
// what the device actually does.
assign state = bus_reset ? S_DEFAULT
: (pend_kind == P_ADDR) ? S_ADDR_PEND
: (pend_kind == P_CONF) ? S_CONFIG_PEND
: conf_r ? S_CONFIGURED
: have_addr ? S_ADDRESSED
: S_DEFAULT;
assign dev_addr = addr_r;
assign configured = conf_r;
assign addressed = have_addr;
assign n_setup = setup_c;
assign n_addr_change = chg_c;
assign n_early_switch = early_c;
assign n_reset_to_default = rst_c;
assign n_wrong_addr = wrong_c;
assign n_dropped_request = drop_c;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
addr_r <= 7'd0;
pend_addr <= 7'd0;
pend_kind <= P_NONE;
addr_at_req <= 7'd0;
have_addr <= 1'b0;
conf_r <= 1'b0;
setup_c <= '0;
chg_c <= 32'd0;
early_c <= 32'd0;
rst_c <= 32'd0;
wrong_c <= 32'd0;
drop_c <= 32'd0;
end else if (bus_reset) begin
// ---- a bus reset returns the device to DEFAULT ----
//
// Address 0, unaddressed, unconfigured, from any state, every time.
// This is why enumeration can always be restarted, and why a wedged
// device recovers on replug: the host resets the port first.
addr_r <= 7'd0;
pend_addr <= 7'd0;
pend_kind <= P_NONE;
addr_at_req <= 7'd0;
have_addr <= 1'b0;
conf_r <= 1'b0;
rst_c <= rst_c + 32'd1;
end else begin
// A token addressed to somebody else is not ours to act on.
if (tok_valid && (tok_addr != addr_r) && (tok_pid != T_SOF))
wrong_c <= wrong_c + 32'd1;
// =============================================================
// STATUS STAGE FIRST.
//
// It resolves whatever was outstanding BEFORE this cycle. Running
// it before the SETUP decode below means a new request arriving on
// the same edge overwrites `pend_kind` afterwards and is therefore
// kept -- rather than being cancelled by this block's write.
// =============================================================
if (status_ack) begin
pend_kind <= P_NONE;
if (pend_kind == P_ADDR) begin
// NOW. Not earlier. The acknowledgement has already been sent
// from the old address and the host has seen it.
addr_r <= pend_addr;
have_addr <= 1'b1;
// Re-addressing returns the device to the Address state, which
// is by definition unconfigured.
conf_r <= 1'b0;
chg_c <= chg_c + 32'd1;
end else if (pend_kind == P_CONF) begin
conf_r <= 1'b1;
end
end
// =============================================================
// SETUP DECODE SECOND, so the newest request wins.
// =============================================================
if (setup_valid) begin
setup_c <= setup_c + 32'd1;
// A SETUP that displaces a still-outstanding request is counted.
// It is legal -- the host may abandon a transfer at any time --
// but a design that does it by accident looks identical, so the
// number is published rather than assumed to be zero.
if ((pend_kind != P_NONE) && !status_ack)
drop_c <= drop_c + 32'd1;
case (req)
R_SET_ADDRESS: begin
// Recorded. NOT applied.
pend_addr <= val[6:0];
pend_kind <= P_ADDR;
// The address that will be LIVE after this edge -- which is not
// addr_r when a status stage is completing a previous
// SET_ADDRESS on this same edge.
addr_at_req <= (status_ack && (pend_kind == P_ADDR)) ? pend_addr
: addr_r;
end
R_SET_CONFIG: begin
// Only meaningful once a SET_ADDRESS has actually COMPLETED.
// Gated on have_addr rather than on the address being
// non-zero, because SET_ADDRESS(0) is legal and means
// "return to the default address".
if (have_addr) pend_kind <= P_CONF;
else pend_kind <= P_NONE;
end
R_GET_DESCRIPTOR: begin
// Legal in every state, including DEFAULT at address 0 -- it
// is how the host learns the packet size it must use for
// everything that follows. It leaves no state pending.
pend_kind <= P_NONE;
end
default: pend_kind <= P_NONE;
endcase
end
// ---- the self-check that makes the central rule measurable ----
//
// While a SET_ADDRESS is outstanding, the live address must not have
// MOVED from what it was when the request arrived. On a correct design
// this is unreachable; it is counted rather than asserted so that a run
// can publish the number zero.
//
// The first version of this check compared addr_r against pend_addr,
// which fires on the perfectly legal SET_ADDRESS(0) to a device that is
// already at address 0 -- 9607 false positives in one run. A checker
// that cries wolf is a checker somebody switches off.
if ((pend_kind == P_ADDR) && (addr_r != addr_at_req))
early_c <= early_c + 32'd1;
end
end
endmodule8. VHDL-2008 RTL
-- =====================================================================
-- usb_enum_fsm -- VHDL-2008.
--
-- VHDL gives the two things this design must not confuse -- the device
-- STATE and the OUTSTANDING REQUEST -- genuinely distinct enumeration
-- types, so mixing them is a compile error rather than a silent bug.
--
-- Note the constant names: VHDL identifiers are case-INSENSITIVE, so a
-- package constant `T_IN` and a port `t_in` would be the same name and
-- the port would win silently. Everything here is prefixed.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package en_pkg is
type dev_state_t is (ST_DEFAULT, ST_ADDR_PEND, ST_ADDRESSED,
ST_CONFIG_PEND, ST_CONFIGURED);
-- A separate type from dev_state_t. The whole point.
type pend_t is (PK_NONE, PK_ADDR, PK_CONF);
constant TOK_OUT : std_logic_vector(1 downto 0) := "00";
constant TOK_IN : std_logic_vector(1 downto 0) := "01";
constant TOK_SOF : std_logic_vector(1 downto 0) := "10";
constant TOK_SETUP : std_logic_vector(1 downto 0) := "11";
constant REQ_GET_DESCRIPTOR : std_logic_vector(7 downto 0) := x"06";
constant REQ_SET_ADDRESS : std_logic_vector(7 downto 0) := x"05";
constant REQ_SET_CONFIG : std_logic_vector(7 downto 0) := x"09";
function state_code(s : dev_state_t) return std_logic_vector;
end package;
package body en_pkg is
function state_code(s : dev_state_t) return std_logic_vector is
begin
case s is
when ST_DEFAULT => return "000";
when ST_ADDR_PEND => return "001";
when ST_ADDRESSED => return "010";
when ST_CONFIG_PEND => return "011";
when ST_CONFIGURED => return "100";
end case;
end function;
end package body;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.en_pkg.all;
entity usb_enum_fsm is
port (
clk : in std_logic;
rst_n : in std_logic;
tok_valid : in std_logic;
tok_pid : in std_logic_vector(1 downto 0);
tok_addr : in std_logic_vector(6 downto 0);
setup_valid : in std_logic;
req : in std_logic_vector(7 downto 0);
val : in std_logic_vector(15 downto 0);
data_stage : in std_logic;
status_ack : in std_logic;
bus_reset : in std_logic;
dev_addr : out std_logic_vector(6 downto 0);
state : out std_logic_vector(2 downto 0);
configured : out std_logic;
addressed : out std_logic;
n_setup : out std_logic_vector(31 downto 0);
n_addr_change : out std_logic_vector(31 downto 0);
n_early_switch : out std_logic_vector(31 downto 0);
n_reset_to_default : out std_logic_vector(31 downto 0);
n_wrong_addr : out std_logic_vector(31 downto 0);
n_dropped_request : out std_logic_vector(31 downto 0)
);
end entity;
architecture rtl of usb_enum_fsm is
signal addr_r : std_logic_vector(6 downto 0) := (others => '0');
signal pend_addr : std_logic_vector(6 downto 0) := (others => '0');
signal addr_at_req : std_logic_vector(6 downto 0) := (others => '0');
signal pend_kind : pend_t := PK_NONE;
signal have_addr : std_logic := '0';
signal conf_r : std_logic := '0';
signal setup_c, chg_c, early_c, rst_c, wrong_c, drop_c
: unsigned(31 downto 0) := (others => '0');
begin
dev_addr <= addr_r;
configured <= conf_r;
addressed <= have_addr;
-- `state` is a DECODE of the real registers, never a register of its own.
-- Nothing in the design branches on it, so it cannot drift out of step
-- with what the device actually does.
state <= state_code(ST_DEFAULT) when bus_reset = '1' else
state_code(ST_ADDR_PEND) when pend_kind = PK_ADDR else
state_code(ST_CONFIG_PEND) when pend_kind = PK_CONF else
state_code(ST_CONFIGURED) when conf_r = '1' else
state_code(ST_ADDRESSED) when have_addr = '1' else
state_code(ST_DEFAULT);
n_setup <= std_logic_vector(setup_c);
n_addr_change <= std_logic_vector(chg_c);
n_early_switch <= std_logic_vector(early_c);
n_reset_to_default <= std_logic_vector(rst_c);
n_wrong_addr <= std_logic_vector(wrong_c);
n_dropped_request <= std_logic_vector(drop_c);
process(clk, rst_n)
begin
if rst_n = '0' then
addr_r <= (others => '0');
pend_addr <= (others => '0');
addr_at_req <= (others => '0');
pend_kind <= PK_NONE;
have_addr <= '0';
conf_r <= '0';
setup_c <= (others => '0');
chg_c <= (others => '0');
early_c <= (others => '0');
rst_c <= (others => '0');
wrong_c <= (others => '0');
drop_c <= (others => '0');
elsif rising_edge(clk) then
if bus_reset = '1' then
-- A bus reset returns the device to DEFAULT: address 0,
-- unaddressed, unconfigured, from any state, every time.
addr_r <= (others => '0');
pend_addr <= (others => '0');
addr_at_req <= (others => '0');
pend_kind <= PK_NONE;
have_addr <= '0';
conf_r <= '0';
rst_c <= rst_c + 1;
else
if tok_valid = '1' and tok_addr /= addr_r and tok_pid /= TOK_SOF then
wrong_c <= wrong_c + 1;
end if;
-- ===========================================================
-- STATUS STAGE FIRST, so a new request arriving on this same
-- edge overwrites pend_kind afterwards and is therefore kept.
-- ===========================================================
if status_ack = '1' then
pend_kind <= PK_NONE;
if pend_kind = PK_ADDR then
-- NOW. The acknowledgement has already gone out from the
-- old address and the host has seen it.
addr_r <= pend_addr;
have_addr <= '1';
conf_r <= '0'; -- re-addressing unconfigures
chg_c <= chg_c + 1;
elsif pend_kind = PK_CONF then
conf_r <= '1';
end if;
end if;
-- ===========================================================
-- SETUP DECODE SECOND, so the newest request wins.
-- ===========================================================
if setup_valid = '1' then
setup_c <= setup_c + 1;
if pend_kind /= PK_NONE and status_ack = '0' then
drop_c <= drop_c + 1;
end if;
if req = REQ_SET_ADDRESS then
-- Recorded. NOT applied.
pend_addr <= val(6 downto 0);
pend_kind <= PK_ADDR;
-- The address that will be LIVE after this edge, which is not
-- addr_r when a status stage is completing a previous
-- SET_ADDRESS on this same edge.
if status_ack = '1' and pend_kind = PK_ADDR then
addr_at_req <= pend_addr;
else
addr_at_req <= addr_r;
end if;
elsif req = REQ_SET_CONFIG then
-- Only once a SET_ADDRESS has actually COMPLETED. Gated on
-- have_addr rather than on the address being non-zero,
-- because SET_ADDRESS(0) is legal and means "return to the
-- default address".
if have_addr = '1' then pend_kind <= PK_CONF;
else pend_kind <= PK_NONE;
end if;
elsif req = REQ_GET_DESCRIPTOR then
-- Legal in every state, including DEFAULT at address 0.
pend_kind <= PK_NONE;
else
pend_kind <= PK_NONE;
end if;
end if;
-- ---- the self-check that makes the central rule measurable ----
--
-- While a SET_ADDRESS is outstanding the live address must not have
-- MOVED from what it was when the request arrived. Unreachable on a
-- correct design, and counted rather than asserted so a run can
-- publish the number zero.
if pend_kind = PK_ADDR and addr_r /= addr_at_req then
early_c <= early_c + 1;
end if;
end if;
end if;
end process;
end architecture;9. How the Testbench Knows the Answer
The shadow model does not mirror the FSM. It answers a different question: "what is the last address a completed SET_ADDRESS status stage established, since the most recent bus reset?" — a claim about completed transfers rather than about states.
That independence had to be fought for, and the fight is the most useful thing in this chapter.
First version of the shadow (WRONG):
if (setup_valid && req == SET_ADDRESS)
s_pend_valid = 1; // blocking
if (status_ack && s_pend_valid)
s_addr = s_pend; // sees the line above!
The model applied the address in the SAME CYCLE the request
arrived -- which is precisely the defect under test. It was
asserting the bug and calling the correct design wrong:
145,208 errors, 7248 "premature switches", design fine.The fix is to snapshot the outstanding-request state as it stood before the cycle, and resolve the status stage against that snapshot — which is exactly what the hardware does, because the hardware reads registered values.
have_addr needed the same treatment for the same reason: the device gates SET_CONFIGURATION on its registered have_addr, so a SET_ADDRESS completing on the same edge does not yet authorise a SET_CONFIGURATION arriving with it. A shadow that had already updated its own copy disagreed 28,386 times.
Verilog-2005 testbench
// =====================================================================
// Testbench for usb_enum_fsm.
//
// The shadow model does not mirror the FSM. It answers a different
// question: "what is the last address a COMPLETED SET_ADDRESS status
// stage established, since the most recent bus reset?" -- a claim about
// completed transfers rather than about states, so a defect in the state
// machine cannot hide inside an identical state machine.
// =====================================================================
`timescale 1ns/1ps
module tb_en_v;
localparam [1:0] T_OUT = 2'd0, T_IN = 2'd1, T_SOF = 2'd2, T_SETUP = 2'd3;
localparam [7:0] R_GET_DESCRIPTOR = 8'h06, R_SET_ADDRESS = 8'h05,
R_SET_CONFIG = 8'h09, R_OTHER = 8'h0B;
localparam [2:0] S_DEFAULT = 3'd0, S_ADDR_PEND = 3'd1, S_ADDRESSED = 3'd2,
S_CONFIG_PEND = 3'd3, S_CONFIGURED = 3'd4;
reg clk = 1'b0, rst_n = 1'b0;
reg tok_valid = 1'b0;
reg [1:0] tok_pid = T_OUT;
reg [6:0] tok_addr = 7'd0;
reg setup_valid = 1'b0;
reg [7:0] req = 8'd0;
reg [15:0] val = 16'd0;
reg data_stage = 1'b0;
reg status_ack = 1'b0;
reg bus_reset = 1'b0;
wire [6:0] dev_addr;
wire [2:0] state;
wire configured, addressed;
wire [31:0] n_setup, n_addr_change, n_early_switch,
n_reset_to_default, n_wrong_addr, n_dropped_request;
usb_enum_fsm dut (
.clk(clk), .rst_n(rst_n),
.tok_valid(tok_valid), .tok_pid(tok_pid), .tok_addr(tok_addr),
.setup_valid(setup_valid), .req(req), .val(val),
.data_stage(data_stage), .status_ack(status_ack),
.bus_reset(bus_reset),
.dev_addr(dev_addr), .state(state),
.configured(configured), .addressed(addressed),
.n_setup(n_setup), .n_addr_change(n_addr_change),
.n_early_switch(n_early_switch),
.n_reset_to_default(n_reset_to_default), .n_wrong_addr(n_wrong_addr),
.n_dropped_request(n_dropped_request)
);
always #5 clk = ~clk;
integer errors = 0, checks = 0, steps = 0;
integer seed;
// ---- $random is SIGNED: mask the sign bit before any modulo ----
function [31:0] urand;
input dummy;
begin urand = $random(seed) & 32'h3FFF_FFFF; end
endfunction
// ---- the shadow: a claim about completed transfers ----
reg [6:0] s_addr; // address established by the last completed SET_ADDRESS
reg [6:0] s_pend; // address a SETUP has requested but not yet established
reg s_pend_valid; // a SET_ADDRESS is outstanding
reg s_conf; // configured
reg s_conf_pend;
reg s_have_addr; // a SET_ADDRESS status stage has completed
// Snapshots of the outstanding-request state as it stood BEFORE this
// cycle. The design decides the status stage from its REGISTERED state,
// so a shadow using blocking assignments would apply a SET_ADDRESS in
// the same cycle its SETUP arrived -- which is precisely the defect
// under test, asserted by the model instead of checked.
reg p_pend_valid, p_conf_pend, p_have_addr;
reg [6:0] p_pend;
reg [31:0] x_setup, x_chg, x_rst, x_wrong, x_drop;
// ---- the headline counter ----
//
// Every cycle in which the live address differed from the address the
// last COMPLETED status stage established. On a correct device this is
// structurally impossible, and the run publishes the number.
integer n_premature = 0;
// ---- exhaustive reach over (state, request, ack, reset) ----
reg reach [0:79];
integer ri, n_reach;
// ---- and over every address SET_ADDRESS can name ----
reg areach [0:127];
integer ai2, n_areach;
task ck(input cond, input [255:0] what);
begin
checks = checks + 1;
if (!cond) begin
errors = errors + 1;
if (errors <= 20)
$display(" ERROR @%0t step=%0d: %0s", $time, steps, what);
end
end
endtask
// ---------------------------------------------------------------
// One bus cycle.
// ---------------------------------------------------------------
task step(input sv, input [7:0] rq, input [15:0] vl,
input ds, input sa, input br,
input tv, input [1:0] tp, input [6:0] ta);
reg [6:0] e_addr;
reg e_conf;
begin
setup_valid = sv; req = rq; val = vl;
data_stage = ds; status_ack = sa; bus_reset = br;
tok_valid = tv; tok_pid = tp; tok_addr = ta;
// ---- advance the shadow to what SHOULD hold after this edge ----
if (br) begin
s_addr = 7'd0; s_pend = 7'd0; s_pend_valid = 1'b0;
s_conf = 1'b0; s_conf_pend = 1'b0; s_have_addr = 1'b0;
x_rst = x_rst + 1;
end else begin
if (tv && (tp != T_SOF) && (ta != s_addr)) x_wrong = x_wrong + 1;
// Snapshot of what was outstanding BEFORE this cycle. The status
// stage resolves THAT, never a request arriving on the same edge.
p_pend_valid = s_pend_valid;
p_pend = s_pend;
p_conf_pend = s_conf_pend;
// have_addr is snapshotted too: the device gates SET_CONFIG on its
// REGISTERED value, so a SET_ADDRESS completing on this same edge
// does not yet authorise a SET_CONFIG arriving with it.
p_have_addr = s_have_addr;
// ---- status stage first, exactly as the device orders it ----
if (sa) begin
s_pend_valid = 1'b0;
s_conf_pend = 1'b0;
if (p_pend_valid) begin
s_addr = p_pend; s_have_addr = 1'b1;
s_conf = 1'b0; // re-addressing unconfigures
x_chg = x_chg + 1;
end else if (p_conf_pend) begin
s_conf = 1'b1;
end
end
// ---- then the new request, which displaces whatever was there ----
if (sv) begin
x_setup = x_setup + 1;
if (p_pend_valid || p_conf_pend) begin
if (!sa) x_drop = x_drop + 1;
end
if (rq == R_SET_ADDRESS) begin
s_pend = vl[6:0]; s_pend_valid = 1'b1; s_conf_pend = 1'b0;
end else if (rq == R_SET_CONFIG) begin
s_pend_valid = 1'b0;
s_conf_pend = p_have_addr;
end else begin
s_pend_valid = 1'b0; s_conf_pend = 1'b0;
end
end
end
e_addr = s_addr;
e_conf = s_conf;
@(posedge clk);
#1;
steps = steps + 1;
// ---- PROPERTY 1: the live address is the last COMPLETED one ----
if (dev_addr !== e_addr) n_premature = n_premature + 1;
ck(dev_addr === e_addr, "live address is not the last completed address");
// ---- PROPERTY 2: configured only after its own status stage ----
ck(configured === e_conf, "configured flag disagrees");
// ---- PROPERTY 3: a pending SET_ADDRESS has NOT taken effect ----
//
// The rule stated directly: while a SET_ADDRESS is outstanding the
// device must still be answering at the address it had before.
if (s_pend_valid && (s_pend != s_addr))
ck(dev_addr !== s_pend,
"device switched to the new address before the status stage");
// ---- PROPERTY 4: counters agree with an independent tally ----
ck(n_setup === x_setup, "SETUP count disagrees");
ck(n_addr_change === x_chg, "address-change count disagrees");
ck(n_reset_to_default === x_rst, "reset count disagrees");
ck(n_wrong_addr === x_wrong, "wrong-address count disagrees");
ck(n_dropped_request === x_drop, "dropped-request count disagrees");
// ---- PROPERTY 5: the design's own early-switch counter is 0 ----
ck(n_early_switch === 32'd0, "the design detected its own early switch");
ck(n_premature == 0, "the address changed before the status stage");
setup_valid = 1'b0; status_ack = 1'b0; bus_reset = 1'b0;
tok_valid = 1'b0; data_stage = 1'b0;
end
endtask
task idle; begin step(0,8'd0,16'd0,0,0,0,0,T_SOF,7'd0); end endtask
task reset_dut;
begin
rst_n = 1'b0;
setup_valid = 0; status_ack = 0; bus_reset = 0; tok_valid = 0;
@(posedge clk); @(posedge clk);
rst_n = 1'b1;
s_addr = 7'd0; s_pend = 7'd0; s_pend_valid = 1'b0;
s_conf = 1'b0; s_conf_pend = 1'b0; s_have_addr = 1'b0;
x_setup = 0; x_chg = 0; x_rst = 0; x_wrong = 0; x_drop = 0;
@(posedge clk); #1;
end
endtask
// ---- drive the device into a named state, the way the host would ----
//
// Never by forcing. A state reached by poking registers is not a state
// the design can actually be in.
task goto_state(input [2:0] want);
begin
reset_dut;
if (want == S_DEFAULT) begin
// already there
end else if (want == S_ADDR_PEND) begin
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
end else if (want == S_ADDRESSED) begin
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
step(0,8'd0,16'd0,1,0,0,0,T_SOF,7'd0);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
end else if (want == S_CONFIG_PEND) begin
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
end else begin // S_CONFIGURED
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
end
end
endtask
integer si, qi, ki, bi, k;
reg [6:0] ea, eb;
reg [7:0] reqs [0:3];
initial begin
for (ri = 0; ri < 80; ri = ri + 1) reach[ri] = 1'b0;
for (ai2 = 0; ai2 < 128; ai2 = ai2 + 1) areach[ai2] = 1'b0;
reqs[0] = R_GET_DESCRIPTOR; reqs[1] = R_SET_ADDRESS;
reqs[2] = R_SET_CONFIG; reqs[3] = R_OTHER;
seed = 32'd27003;
// =============================================================
// PHASE 1 (DIRECTED, EXHAUSTIVE) -- every request in every
// state, with and without a status stage, with and without a
// bus reset. 5 x 4 x 2 x 2 = 80.
// =============================================================
for (si = 0; si < 5; si = si + 1)
for (qi = 0; qi < 4; qi = qi + 1)
for (ki = 0; ki < 2; ki = ki + 1)
for (bi = 0; bi < 2; bi = bi + 1) begin
goto_state(si[2:0]);
step(1, reqs[qi], 16'd42, 0, ki[0], bi[0], 1, T_SETUP, dev_addr);
idle;
idle;
ri = (si * 16) + (qi * 4) + (ki * 2) + bi;
reach[ri] = 1'b1;
end
// =============================================================
// PHASE 2 (DIRECTED, EXHAUSTIVE) -- every address 0..127.
//
// SET_ADDRESS to each value in turn, checking at every step that
// the old address is still live until the status stage. One
// address proves nothing: the interesting values are 0 (which is
// legal and means "go back to default") and 127 (the maximum).
// =============================================================
for (k = 0; k < 128; k = k + 1) begin
reset_dut;
step(1, R_SET_ADDRESS, {9'd0, k[6:0]}, 0, 0, 0, 1, T_SETUP, 7'd0);
// the device is STILL address 0 here, for as long as the host takes
idle;
idle;
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
// and only now is it address k
idle;
areach[k] = 1'b1;
end
// =============================================================
// PHASE 3 (DIRECTED) -- a bus reset from every state.
//
// A reset returns the device to DEFAULT and address 0 from
// anywhere. This is the recovery path the whole protocol leans on.
// =============================================================
for (si = 0; si < 5; si = si + 1) begin
goto_state(si[2:0]);
step(0, 8'd0, 16'd0, 0, 0, 1, 0, T_SOF, 7'd0);
ck(dev_addr === 7'd0, "a bus reset did not return the device to address 0");
ck(state === S_DEFAULT, "a bus reset did not return the device to DEFAULT");
ck(configured === 1'b0, "a bus reset left the device configured");
idle;
end
// =============================================================
// PHASE 4 (DIRECTED) -- a status stage with nothing outstanding.
//
// A stray ACK must change nothing. This is the mirror image of the
// central rule and it is where a design that keys off the wrong
// event shows up.
// =============================================================
for (si = 0; si < 5; si = si + 1) begin
goto_state(si[2:0]);
for (k = 0; k < 4; k = k + 1)
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
end
// =============================================================
// PHASE 4b (DIRECTED, EXHAUSTIVE) -- every request COMPLETED.
//
// Phase 1 issues a request in every state but never follows it with
// a status stage, so nothing it does can be observed: C3 (accepting
// SET_CONFIGURATION in DEFAULT) and C5 (staying configured across a
// re-address) both had a directed score of exactly ZERO.
//
// A request that is never completed changes nothing, so a phase that
// never completes one tests nothing. 5 states x 4 requests.
// =============================================================
for (si = 0; si < 5; si = si + 1)
for (qi = 0; qi < 4; qi = qi + 1) begin
goto_state(si[2:0]);
step(1, reqs[qi], 16'd33, 0, 0, 0, 1, T_SETUP, dev_addr);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr); // complete it
idle;
// and a second completion, which must change nothing further
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
idle;
end
// =============================================================
// PHASE 4c (DIRECTED, EXHAUSTIVE) -- a REAL enumeration, eight times.
//
// This is the chapter's question executed as stimulus: the whole
// lifecycle from attach to configured, including the two steps that
// only make sense in sequence -- a SET_CONFIGURATION refused because
// no address has been established, and a re-address that must
// UNCONFIGURE the device.
//
// Phase 4b reaches each of those situations exactly once, which gave
// three mutations directed scores of 8, 4 and 4. Eight passes over
// eight different address pairs turns luck into arithmetic.
// =============================================================
for (k = 0; k < 8; k = k + 1) begin
ea = 7'd1 + k[6:0] * 7'd7;
eb = 7'd120 - k[6:0] * 7'd7;
reset_dut;
// 1. SET_CONFIGURATION before any address has been established.
// Refused -- and refused even though the host completes the
// transfer, which is the part that makes C3 observable.
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(configured === 1'b0, "configured without ever having an address");
ck(addressed === 1'b0, "addressed without a completed SET_ADDRESS");
// 2. GET_DESCRIPTOR at address 0. Legal, and how the host learns
// the packet size it must use for everything after this.
step(1, R_GET_DESCRIPTOR, 16'd0, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(dev_addr === 7'd0, "GET_DESCRIPTOR moved the device off address 0");
// 2b. A SET_ADDRESS that is ABANDONED before its status stage.
//
// The host asks, then changes its mind and asks for a
// configuration instead. Two things must hold: the device is
// still NOT addressed while the request is merely outstanding,
// and the SET_CONFIGURATION is therefore refused. A device that
// marks itself addressed when the request ARRIVES passes every
// other check in this bench and fails both of these.
step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
ck(dev_addr === 7'd0, "address changed before the status stage");
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(configured === 1'b0, "configured off an abandoned SET_ADDRESS");
ck(dev_addr === 7'd0, "an abandoned SET_ADDRESS still took effect");
ck(addressed === 1'b0, "addressed off an abandoned SET_ADDRESS");
// 3. SET_ADDRESS. The old address stays live until the status stage.
step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
ck(dev_addr === 7'd0, "address changed before the status stage");
idle;
ck(dev_addr === 7'd0, "address changed before the status stage");
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(dev_addr === ea, "address did not take effect after the status stage");
ck(addressed === 1'b1, "device not addressed after a completed SET_ADDRESS");
// 4. Now SET_CONFIGURATION is meaningful.
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, ea);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
idle;
ck(configured === 1'b1, "device did not configure when it should have");
// 5. Re-address. The configuration survives until the status stage
// and must be gone immediately after it.
step(1, R_SET_ADDRESS, {9'd0, eb}, 0, 0, 0, 1, T_SETUP, ea);
idle;
ck(dev_addr === ea, "address changed before the status stage");
ck(configured === 1'b1, "configuration dropped before the re-address completed");
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
idle;
ck(dev_addr === eb, "re-address did not take effect");
ck(configured === 1'b0, "device stayed configured across a re-address");
// 6. And it can be configured again at the new address.
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, eb);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, eb);
idle;
ck(configured === 1'b1, "device could not be reconfigured after a re-address");
end
// =============================================================
// PHASE 5 (RANDOM) -- a host doing all of it in any order.
// =============================================================
`ifndef DIRECTED_ONLY
reset_dut;
for (k = 0; k < 30000; k = k + 1) begin
step((urand(0) % 4) == 0, // setup_valid
reqs[urand(0) % 4],
{9'd0, urand(0)} & 16'h007F,
(urand(0) % 3) == 0, // data_stage
(urand(0) % 4) == 0, // status_ack
(urand(0) % 200) == 0, // bus_reset, rare
(urand(0) % 2) == 0, // tok_valid
urand(0) % 4,
((urand(0) % 3) == 0) ? (urand(0) & 7'h7F) : dev_addr);
end
`endif
n_reach = 0;
for (ri = 0; ri < 80; ri = ri + 1) if (reach[ri]) n_reach = n_reach + 1;
n_areach = 0;
for (ai2 = 0; ai2 < 128; ai2 = ai2 + 1) if (areach[ai2]) n_areach = n_areach + 1;
$display("steps=%0d checks=%0d reach=%0d/80 addrs=%0d/128 errors=%0d",
steps, checks, n_reach, n_areach, errors);
$display("[enum] setups=%0d addr_changes=%0d resets=%0d wrong_addr=%0d dropped=%0d",
n_setup, n_addr_change, n_reset_to_default, n_wrong_addr,
n_dropped_request);
$display("[the whole point] premature address switches = %0d", n_premature);
if (n_reach != 80 || n_areach != 128) begin
$display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
end
if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
else $display("FAIL: %0d errors in %0d checks", errors, checks);
$finish;
end
endmoduleSystemVerilog testbench
// =====================================================================
// Testbench for usb_enum_fsm.
//
// The shadow model does not mirror the FSM. It answers a different
// question: "what is the last address a COMPLETED SET_ADDRESS status
// stage established, since the most recent bus reset?" -- a claim about
// completed transfers rather than about states, so a defect in the state
// machine cannot hide inside an identical state machine.
// =====================================================================
`timescale 1ns/1ps
module tb_en_sv;
localparam [1:0] T_OUT = 2'd0, T_IN = 2'd1, T_SOF = 2'd2, T_SETUP = 2'd3;
localparam [7:0] R_GET_DESCRIPTOR = 8'h06, R_SET_ADDRESS = 8'h05,
R_SET_CONFIG = 8'h09, R_OTHER = 8'h0B;
localparam [2:0] S_DEFAULT = 3'd0, S_ADDR_PEND = 3'd1, S_ADDRESSED = 3'd2,
S_CONFIG_PEND = 3'd3, S_CONFIGURED = 3'd4;
logic clk = 1'b0, rst_n = 1'b0;
logic tok_valid = 1'b0;
logic [1:0] tok_pid = T_OUT;
logic [6:0] tok_addr = 7'd0;
logic setup_valid = 1'b0;
logic [7:0] req = 8'd0;
logic [15:0] val = 16'd0;
logic data_stage = 1'b0;
logic status_ack = 1'b0;
logic bus_reset = 1'b0;
logic [6:0] dev_addr;
logic [2:0] state;
logic configured, addressed;
logic [31:0] n_setup, n_addr_change, n_early_switch,
n_reset_to_default, n_wrong_addr, n_dropped_request;
usb_enum_fsm dut (
.clk(clk), .rst_n(rst_n),
.tok_valid(tok_valid), .tok_pid(tok_pid), .tok_addr(tok_addr),
.setup_valid(setup_valid), .req(req), .val(val),
.data_stage(data_stage), .status_ack(status_ack),
.bus_reset(bus_reset),
.dev_addr(dev_addr), .state(state),
.configured(configured), .addressed(addressed),
.n_setup(n_setup), .n_addr_change(n_addr_change),
.n_early_switch(n_early_switch),
.n_reset_to_default(n_reset_to_default), .n_wrong_addr(n_wrong_addr),
.n_dropped_request(n_dropped_request)
);
always #5 clk = ~clk;
integer errors = 0, checks = 0, steps = 0;
integer seed;
// ---- $random is SIGNED: mask the sign bit before any modulo ----
function automatic logic [31:0] urand(bit dummy);
return $random(seed) & 32'h3FFF_FFFF;
endfunction
// ---- the shadow: a claim about completed transfers ----
logic [6:0] s_addr; // address established by the last completed SET_ADDRESS
logic [6:0] s_pend; // address a SETUP has requested but not yet established
logic s_pend_valid; // a SET_ADDRESS is outstanding
logic s_conf; // configured
logic s_conf_pend;
logic s_have_addr; // a SET_ADDRESS status stage has completed
// Snapshots of the outstanding-request state as it stood BEFORE this
// cycle. The design decides the status stage from its REGISTERED state,
// so a shadow using blocking assignments would apply a SET_ADDRESS in
// the same cycle its SETUP arrived -- which is precisely the defect
// under test, asserted by the model instead of checked.
logic p_pend_valid, p_conf_pend, p_have_addr;
logic [6:0] p_pend;
logic [31:0] x_setup, x_chg, x_rst, x_wrong, x_drop;
// ---- the headline counter ----
//
// Every cycle in which the live address differed from the address the
// last COMPLETED status stage established. On a correct device this is
// structurally impossible, and the run publishes the number.
integer n_premature = 0;
// ---- exhaustive reach over (state, request, ack, reset) ----
logic reach [0:79];
integer ri, n_reach;
// ---- and over every address SET_ADDRESS can name ----
logic areach [0:127];
integer ai2, n_areach;
task ck(input logic cond, input logic [255:0] what);
begin
checks = checks + 1;
if (!cond) begin
errors = errors + 1;
if (errors <= 20)
$display(" ERROR @%0t step=%0d: %0s", $time, steps, what);
end
end
endtask
// ---------------------------------------------------------------
// One bus cycle.
// ---------------------------------------------------------------
task step(input logic sv, input logic [7:0] rq, input logic [15:0] vl,
input logic ds, input logic sa, input logic br,
input logic tv, input logic [1:0] tp, input logic [6:0] ta);
logic [6:0] e_addr;
logic e_conf;
begin
setup_valid = sv; req = rq; val = vl;
data_stage = ds; status_ack = sa; bus_reset = br;
tok_valid = tv; tok_pid = tp; tok_addr = ta;
// ---- advance the shadow to what SHOULD hold after this edge ----
if (br) begin
s_addr = 7'd0; s_pend = 7'd0; s_pend_valid = 1'b0;
s_conf = 1'b0; s_conf_pend = 1'b0; s_have_addr = 1'b0;
x_rst = x_rst + 1;
end else begin
if (tv && (tp != T_SOF) && (ta != s_addr)) x_wrong = x_wrong + 1;
// Snapshot of what was outstanding BEFORE this cycle. The status
// stage resolves THAT, never a request arriving on the same edge.
p_pend_valid = s_pend_valid;
p_pend = s_pend;
p_conf_pend = s_conf_pend;
// have_addr is snapshotted too: the device gates SET_CONFIG on its
// REGISTERED value, so a SET_ADDRESS completing on this same edge
// does not yet authorise a SET_CONFIG arriving with it.
p_have_addr = s_have_addr;
// ---- status stage first, exactly as the device orders it ----
if (sa) begin
s_pend_valid = 1'b0;
s_conf_pend = 1'b0;
if (p_pend_valid) begin
s_addr = p_pend; s_have_addr = 1'b1;
s_conf = 1'b0; // re-addressing unconfigures
x_chg = x_chg + 1;
end else if (p_conf_pend) begin
s_conf = 1'b1;
end
end
// ---- then the new request, which displaces whatever was there ----
if (sv) begin
x_setup = x_setup + 1;
if (p_pend_valid || p_conf_pend) begin
if (!sa) x_drop = x_drop + 1;
end
if (rq == R_SET_ADDRESS) begin
s_pend = vl[6:0]; s_pend_valid = 1'b1; s_conf_pend = 1'b0;
end else if (rq == R_SET_CONFIG) begin
s_pend_valid = 1'b0;
s_conf_pend = p_have_addr;
end else begin
s_pend_valid = 1'b0; s_conf_pend = 1'b0;
end
end
end
e_addr = s_addr;
e_conf = s_conf;
@(posedge clk);
#1;
steps = steps + 1;
// ---- PROPERTY 1: the live address is the last COMPLETED one ----
if (dev_addr !== e_addr) n_premature = n_premature + 1;
ck(dev_addr === e_addr, "live address is not the last completed address");
// ---- PROPERTY 2: configured only after its own status stage ----
ck(configured === e_conf, "configured flag disagrees");
// ---- PROPERTY 3: a pending SET_ADDRESS has NOT taken effect ----
//
// The rule stated directly: while a SET_ADDRESS is outstanding the
// device must still be answering at the address it had before.
if (s_pend_valid && (s_pend != s_addr))
ck(dev_addr !== s_pend,
"device switched to the new address before the status stage");
// ---- PROPERTY 4: counters agree with an independent tally ----
ck(n_setup === x_setup, "SETUP count disagrees");
ck(n_addr_change === x_chg, "address-change count disagrees");
ck(n_reset_to_default === x_rst, "reset count disagrees");
ck(n_wrong_addr === x_wrong, "wrong-address count disagrees");
ck(n_dropped_request === x_drop, "dropped-request count disagrees");
// ---- PROPERTY 5: the design's own early-switch counter is 0 ----
ck(n_early_switch === 32'd0, "the design detected its own early switch");
ck(n_premature == 0, "the address changed before the status stage");
setup_valid = 1'b0; status_ack = 1'b0; bus_reset = 1'b0;
tok_valid = 1'b0; data_stage = 1'b0;
end
endtask
task idle; begin step(0,8'd0,16'd0,0,0,0,0,T_SOF,7'd0); end endtask
task reset_dut;
begin
rst_n = 1'b0;
setup_valid = 0; status_ack = 0; bus_reset = 0; tok_valid = 0;
@(posedge clk); @(posedge clk);
rst_n = 1'b1;
s_addr = 7'd0; s_pend = 7'd0; s_pend_valid = 1'b0;
s_conf = 1'b0; s_conf_pend = 1'b0; s_have_addr = 1'b0;
x_setup = 0; x_chg = 0; x_rst = 0; x_wrong = 0; x_drop = 0;
@(posedge clk); #1;
end
endtask
// ---- drive the device into a named state, the way the host would ----
//
// Never by forcing. A state reached by poking registers is not a state
// the design can actually be in.
task goto_state(input logic [2:0] want);
begin
reset_dut;
if (want == S_DEFAULT) begin
// already there
end else if (want == S_ADDR_PEND) begin
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
end else if (want == S_ADDRESSED) begin
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
step(0,8'd0,16'd0,1,0,0,0,T_SOF,7'd0);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
end else if (want == S_CONFIG_PEND) begin
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
end else begin // S_CONFIGURED
step(1,R_SET_ADDRESS,16'd7,0,0,0,1,T_SETUP,7'd0);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
step(1,R_SET_CONFIG,16'd1,0,0,0,1,T_SETUP,7'd7);
step(0,8'd0,16'd0,0,1,0,0,T_SOF,7'd0);
end
end
endtask
integer si, qi, ki, bi, k;
logic [6:0] ea, eb;
logic [7:0] reqs [0:3];
initial begin
for (ri = 0; ri < 80; ri = ri + 1) reach[ri] = 1'b0;
for (ai2 = 0; ai2 < 128; ai2 = ai2 + 1) areach[ai2] = 1'b0;
reqs[0] = R_GET_DESCRIPTOR; reqs[1] = R_SET_ADDRESS;
reqs[2] = R_SET_CONFIG; reqs[3] = R_OTHER;
seed = 32'd27003;
// =============================================================
// PHASE 1 (DIRECTED, EXHAUSTIVE) -- every request in every
// state, with and without a status stage, with and without a
// bus reset. 5 x 4 x 2 x 2 = 80.
// =============================================================
for (si = 0; si < 5; si = si + 1)
for (qi = 0; qi < 4; qi = qi + 1)
for (ki = 0; ki < 2; ki = ki + 1)
for (bi = 0; bi < 2; bi = bi + 1) begin
goto_state(si[2:0]);
step(1, reqs[qi], 16'd42, 0, ki[0], bi[0], 1, T_SETUP, dev_addr);
idle;
idle;
ri = (si * 16) + (qi * 4) + (ki * 2) + bi;
reach[ri] = 1'b1;
end
// =============================================================
// PHASE 2 (DIRECTED, EXHAUSTIVE) -- every address 0..127.
//
// SET_ADDRESS to each value in turn, checking at every step that
// the old address is still live until the status stage. One
// address proves nothing: the interesting values are 0 (which is
// legal and means "go back to default") and 127 (the maximum).
// =============================================================
for (k = 0; k < 128; k = k + 1) begin
reset_dut;
step(1, R_SET_ADDRESS, {9'd0, k[6:0]}, 0, 0, 0, 1, T_SETUP, 7'd0);
// the device is STILL address 0 here, for as long as the host takes
idle;
idle;
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
// and only now is it address k
idle;
areach[k] = 1'b1;
end
// =============================================================
// PHASE 3 (DIRECTED) -- a bus reset from every state.
//
// A reset returns the device to DEFAULT and address 0 from
// anywhere. This is the recovery path the whole protocol leans on.
// =============================================================
for (si = 0; si < 5; si = si + 1) begin
goto_state(si[2:0]);
step(0, 8'd0, 16'd0, 0, 0, 1, 0, T_SOF, 7'd0);
ck(dev_addr === 7'd0, "a bus reset did not return the device to address 0");
ck(state === S_DEFAULT, "a bus reset did not return the device to DEFAULT");
ck(configured === 1'b0, "a bus reset left the device configured");
idle;
end
// =============================================================
// PHASE 4 (DIRECTED) -- a status stage with nothing outstanding.
//
// A stray ACK must change nothing. This is the mirror image of the
// central rule and it is where a design that keys off the wrong
// event shows up.
// =============================================================
for (si = 0; si < 5; si = si + 1) begin
goto_state(si[2:0]);
for (k = 0; k < 4; k = k + 1)
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
end
// =============================================================
// PHASE 4b (DIRECTED, EXHAUSTIVE) -- every request COMPLETED.
//
// Phase 1 issues a request in every state but never follows it with
// a status stage, so nothing it does can be observed: C3 (accepting
// SET_CONFIGURATION in DEFAULT) and C5 (staying configured across a
// re-address) both had a directed score of exactly ZERO.
//
// A request that is never completed changes nothing, so a phase that
// never completes one tests nothing. 5 states x 4 requests.
// =============================================================
for (si = 0; si < 5; si = si + 1)
for (qi = 0; qi < 4; qi = qi + 1) begin
goto_state(si[2:0]);
step(1, reqs[qi], 16'd33, 0, 0, 0, 1, T_SETUP, dev_addr);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr); // complete it
idle;
// and a second completion, which must change nothing further
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, dev_addr);
idle;
end
// =============================================================
// PHASE 4c (DIRECTED, EXHAUSTIVE) -- a REAL enumeration, eight times.
//
// This is the chapter's question executed as stimulus: the whole
// lifecycle from attach to configured, including the two steps that
// only make sense in sequence -- a SET_CONFIGURATION refused because
// no address has been established, and a re-address that must
// UNCONFIGURE the device.
//
// Phase 4b reaches each of those situations exactly once, which gave
// three mutations directed scores of 8, 4 and 4. Eight passes over
// eight different address pairs turns luck into arithmetic.
// =============================================================
for (k = 0; k < 8; k = k + 1) begin
ea = 7'd1 + k[6:0] * 7'd7;
eb = 7'd120 - k[6:0] * 7'd7;
reset_dut;
// 1. SET_CONFIGURATION before any address has been established.
// Refused -- and refused even though the host completes the
// transfer, which is the part that makes C3 observable.
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(configured === 1'b0, "configured without ever having an address");
ck(addressed === 1'b0, "addressed without a completed SET_ADDRESS");
// 2. GET_DESCRIPTOR at address 0. Legal, and how the host learns
// the packet size it must use for everything after this.
step(1, R_GET_DESCRIPTOR, 16'd0, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(dev_addr === 7'd0, "GET_DESCRIPTOR moved the device off address 0");
// 2b. A SET_ADDRESS that is ABANDONED before its status stage.
//
// The host asks, then changes its mind and asks for a
// configuration instead. Two things must hold: the device is
// still NOT addressed while the request is merely outstanding,
// and the SET_CONFIGURATION is therefore refused. A device that
// marks itself addressed when the request ARRIVES passes every
// other check in this bench and fails both of these.
step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
ck(dev_addr === 7'd0, "address changed before the status stage");
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
ck(addressed === 1'b0, "device claimed to be addressed before the status stage");
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(configured === 1'b0, "configured off an abandoned SET_ADDRESS");
ck(dev_addr === 7'd0, "an abandoned SET_ADDRESS still took effect");
ck(addressed === 1'b0, "addressed off an abandoned SET_ADDRESS");
// 3. SET_ADDRESS. The old address stays live until the status stage.
step(1, R_SET_ADDRESS, {9'd0, ea}, 0, 0, 0, 1, T_SETUP, 7'd0);
idle;
ck(dev_addr === 7'd0, "address changed before the status stage");
idle;
ck(dev_addr === 7'd0, "address changed before the status stage");
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, 7'd0);
idle;
ck(dev_addr === ea, "address did not take effect after the status stage");
ck(addressed === 1'b1, "device not addressed after a completed SET_ADDRESS");
// 4. Now SET_CONFIGURATION is meaningful.
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, ea);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
idle;
ck(configured === 1'b1, "device did not configure when it should have");
// 5. Re-address. The configuration survives until the status stage
// and must be gone immediately after it.
step(1, R_SET_ADDRESS, {9'd0, eb}, 0, 0, 0, 1, T_SETUP, ea);
idle;
ck(dev_addr === ea, "address changed before the status stage");
ck(configured === 1'b1, "configuration dropped before the re-address completed");
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, ea);
idle;
ck(dev_addr === eb, "re-address did not take effect");
ck(configured === 1'b0, "device stayed configured across a re-address");
// 6. And it can be configured again at the new address.
step(1, R_SET_CONFIG, 16'd1, 0, 0, 0, 1, T_SETUP, eb);
idle;
step(0, 8'd0, 16'd0, 0, 1, 0, 0, T_SOF, eb);
idle;
ck(configured === 1'b1, "device could not be reconfigured after a re-address");
end
// =============================================================
// PHASE 5 (RANDOM) -- a host doing all of it in any order.
// =============================================================
`ifndef DIRECTED_ONLY
reset_dut;
for (k = 0; k < 30000; k = k + 1) begin
step((urand(0) % 4) == 0, // setup_valid
reqs[urand(0) % 4],
{9'd0, urand(0)} & 16'h007F,
(urand(0) % 3) == 0, // data_stage
(urand(0) % 4) == 0, // status_ack
(urand(0) % 200) == 0, // bus_reset, rare
(urand(0) % 2) == 0, // tok_valid
urand(0) % 4,
((urand(0) % 3) == 0) ? (urand(0) & 7'h7F) : dev_addr);
end
`endif
n_reach = 0;
for (ri = 0; ri < 80; ri = ri + 1) if (reach[ri]) n_reach = n_reach + 1;
n_areach = 0;
for (ai2 = 0; ai2 < 128; ai2 = ai2 + 1) if (areach[ai2]) n_areach = n_areach + 1;
$display("steps=%0d checks=%0d reach=%0d/80 addrs=%0d/128 errors=%0d",
steps, checks, n_reach, n_areach, errors);
$display("[enum] setups=%0d addr_changes=%0d resets=%0d wrong_addr=%0d dropped=%0d",
n_setup, n_addr_change, n_reset_to_default, n_wrong_addr,
n_dropped_request);
$display("[the whole point] premature address switches = %0d", n_premature);
if (n_reach != 80 || n_areach != 128) begin
$display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
end
if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
else $display("FAIL: %0d errors in %0d checks", errors, checks);
$finish;
end
endmoduleVHDL-2008 testbench
-- =====================================================================
-- Testbench for usb_enum_fsm (VHDL-2008).
--
-- Same shadow model and the same five phases. The random source is an
-- xorshift unrelated to Icarus's generator, so the VHDL column of the
-- mutation table is a second opinion rather than a third copy.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.en_pkg.all;
entity tb_en_vhdl is
-- No preprocessor: the directed/random split is an elaboration generic.
generic (DIRECTED_ONLY : boolean := false);
end entity;
architecture sim of tb_en_vhdl is
constant REQ_OTHER : std_logic_vector(7 downto 0) := x"0B";
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal tok_valid : std_logic := '0';
signal tok_pid : std_logic_vector(1 downto 0) := TOK_OUT;
signal tok_addr : std_logic_vector(6 downto 0) := (others => '0');
signal setup_valid : std_logic := '0';
signal req : std_logic_vector(7 downto 0) := (others => '0');
signal val : std_logic_vector(15 downto 0) := (others => '0');
signal data_stage : std_logic := '0';
signal status_ack : std_logic := '0';
signal bus_reset : std_logic := '0';
signal dev_addr : std_logic_vector(6 downto 0);
signal state : std_logic_vector(2 downto 0);
signal configured : std_logic;
signal addressed : std_logic;
signal n_setup, n_addr_change, n_early_switch,
n_reset_to_default, n_wrong_addr, n_dropped_request
: std_logic_vector(31 downto 0);
signal done : boolean := false;
begin
dut : entity work.usb_enum_fsm
port map (
clk => clk, rst_n => rst_n,
tok_valid => tok_valid, tok_pid => tok_pid, tok_addr => tok_addr,
setup_valid => setup_valid, req => req, val => val,
data_stage => data_stage, status_ack => status_ack,
bus_reset => bus_reset,
dev_addr => dev_addr, state => state,
configured => configured, addressed => addressed,
n_setup => n_setup, n_addr_change => n_addr_change,
n_early_switch => n_early_switch,
n_reset_to_default => n_reset_to_default,
n_wrong_addr => n_wrong_addr,
n_dropped_request => n_dropped_request);
clk <= not clk after 5 ns when not done else '0';
stim : process
variable errors : natural := 0;
variable checks : natural := 0;
variable steps : natural := 0;
variable n_premature: natural := 0;
variable s_addr, s_pend, p_pend : std_logic_vector(6 downto 0)
:= (others => '0');
variable s_pend_valid, s_conf, s_conf_pend, s_have_addr : std_logic := '0';
variable p_pend_valid, p_conf_pend, p_have_addr : std_logic := '0';
variable x_setup, x_chg, x_rst, x_wrong, x_drop : natural := 0;
variable reach : std_logic_vector(0 to 79) := (others => '0');
variable areach : std_logic_vector(0 to 127) := (others => '0');
variable n_reach, n_areach : natural := 0;
variable rnd : unsigned(31 downto 0) := x"0004C2F9";
variable ln : line;
procedure ck(cond : boolean; what : string) is
begin
checks := checks + 1;
if not cond then
errors := errors + 1;
if errors <= 20 then
write(ln, string'(" ERROR step=") & integer'image(steps)
& string'(": ") & what);
writeline(output, ln);
end if;
end if;
end procedure;
impure function nxt return natural is
begin
rnd := rnd xor (rnd sll 13);
rnd := rnd xor (rnd srl 17);
rnd := rnd xor (rnd sll 5);
return to_integer(rnd(14 downto 0));
end function;
procedure step(sv : std_logic;
rq : std_logic_vector(7 downto 0);
vl : std_logic_vector(15 downto 0);
ds : std_logic; sa : std_logic; br : std_logic;
tv : std_logic;
tp : std_logic_vector(1 downto 0);
ta : std_logic_vector(6 downto 0)) is
variable e_addr : std_logic_vector(6 downto 0);
variable e_conf : std_logic;
begin
setup_valid <= sv; req <= rq; val <= vl;
data_stage <= ds; status_ack <= sa; bus_reset <= br;
tok_valid <= tv; tok_pid <= tp; tok_addr <= ta;
if br = '1' then
s_addr := (others => '0'); s_pend := (others => '0');
s_pend_valid := '0'; s_conf := '0'; s_conf_pend := '0';
s_have_addr := '0';
x_rst := x_rst + 1;
else
if tv = '1' and tp /= TOK_SOF and ta /= s_addr then
x_wrong := x_wrong + 1;
end if;
-- snapshot of what was outstanding BEFORE this cycle
p_pend_valid := s_pend_valid;
p_pend := s_pend;
p_conf_pend := s_conf_pend;
-- have_addr is snapshotted too: the device gates SET_CONFIG on its
-- REGISTERED value, so a SET_ADDRESS completing on this same edge
-- does not yet authorise a SET_CONFIG arriving with it.
p_have_addr := s_have_addr;
-- status stage first, exactly as the device orders it
if sa = '1' then
s_pend_valid := '0';
s_conf_pend := '0';
if p_pend_valid = '1' then
s_addr := p_pend; s_have_addr := '1';
s_conf := '0'; -- re-addressing unconfigures
x_chg := x_chg + 1;
elsif p_conf_pend = '1' then
s_conf := '1';
end if;
end if;
-- then the new request, which displaces whatever was there
if sv = '1' then
x_setup := x_setup + 1;
if (p_pend_valid = '1' or p_conf_pend = '1') and sa = '0' then
x_drop := x_drop + 1;
end if;
if rq = REQ_SET_ADDRESS then
s_pend := vl(6 downto 0); s_pend_valid := '1'; s_conf_pend := '0';
elsif rq = REQ_SET_CONFIG then
s_pend_valid := '0';
s_conf_pend := p_have_addr;
else
s_pend_valid := '0'; s_conf_pend := '0';
end if;
end if;
end if;
e_addr := s_addr;
e_conf := s_conf;
wait until rising_edge(clk);
wait for 1 ns;
steps := steps + 1;
if dev_addr /= e_addr then n_premature := n_premature + 1; end if;
ck(dev_addr = e_addr, "live address is not the last completed address");
ck((configured = '1') = (e_conf = '1'), "configured flag disagrees");
if s_pend_valid = '1' and s_pend /= s_addr then
ck(dev_addr /= s_pend,
"device switched to the new address before the status stage");
end if;
ck(to_integer(unsigned(n_setup)) = x_setup, "SETUP count disagrees");
ck(to_integer(unsigned(n_addr_change)) = x_chg, "address-change count disagrees");
ck(to_integer(unsigned(n_reset_to_default)) = x_rst, "reset count disagrees");
ck(to_integer(unsigned(n_wrong_addr)) = x_wrong, "wrong-address count disagrees");
ck(to_integer(unsigned(n_dropped_request)) = x_drop, "dropped-request count disagrees");
ck(to_integer(unsigned(n_early_switch)) = 0,
"the design detected its own early switch");
ck(n_premature = 0, "the address changed before the status stage");
setup_valid <= '0'; status_ack <= '0'; bus_reset <= '0';
tok_valid <= '0'; data_stage <= '0';
end procedure;
procedure idle is
begin
step('0', x"00", x"0000", '0', '0', '0', '0', TOK_SOF, "0000000");
end procedure;
procedure reset_dut is
begin
rst_n <= '0';
setup_valid <= '0'; status_ack <= '0'; bus_reset <= '0';
tok_valid <= '0';
wait until rising_edge(clk);
wait until rising_edge(clk);
rst_n <= '1';
s_addr := (others => '0'); s_pend := (others => '0');
s_pend_valid := '0'; s_conf := '0'; s_conf_pend := '0';
s_have_addr := '0';
x_setup := 0; x_chg := 0; x_rst := 0; x_wrong := 0; x_drop := 0;
wait until rising_edge(clk);
wait for 1 ns;
end procedure;
-- Drive the device into a named state the way the host would. Never by
-- forcing: a state reached by poking registers is not a state the
-- design can actually be in.
procedure goto_state(want : natural) is
begin
reset_dut;
if want = 1 then
step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
elsif want = 2 then
step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
step('0', x"00", x"0000", '1', '0', '0', '0', TOK_SOF, "0000000");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
elsif want = 3 then
step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, "0000111");
elsif want = 4 then
step('1', REQ_SET_ADDRESS, x"0007", '0', '0', '0', '1', TOK_SETUP, "0000000");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, "0000111");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
end if;
end procedure;
type req_arr is array (0 to 3) of std_logic_vector(7 downto 0);
constant reqs : req_arr := (REQ_GET_DESCRIPTOR, REQ_SET_ADDRESS,
REQ_SET_CONFIG, REQ_OTHER);
variable ri : natural;
variable sa2, br2 : std_logic;
-- VHDL has no inline declare block inside a process body, so the
-- random phase's temporaries live here rather than where they are used.
variable sv3, ds3, sa3, br3, tv3 : std_logic := '0';
variable ta3 : std_logic_vector(6 downto 0);
variable ea, eb : std_logic_vector(6 downto 0);
begin
-- PHASE 1 (DIRECTED, EXHAUSTIVE) -- 5 states x 4 requests x ack x reset
for si in 0 to 4 loop
for qi in 0 to 3 loop
for ki in 0 to 1 loop
for bi in 0 to 1 loop
goto_state(si);
if ki = 1 then sa2 := '1'; else sa2 := '0'; end if;
if bi = 1 then br2 := '1'; else br2 := '0'; end if;
step('1', reqs(qi), x"002A", '0', sa2, br2, '1', TOK_SETUP, dev_addr);
idle;
idle;
ri := si*16 + qi*4 + ki*2 + bi;
reach(ri) := '1';
end loop;
end loop;
end loop;
end loop;
-- PHASE 2 (DIRECTED, EXHAUSTIVE) -- every address 0..127
for k in 0 to 127 loop
reset_dut;
step('1', REQ_SET_ADDRESS, std_logic_vector(to_unsigned(k, 16)),
'0', '0', '0', '1', TOK_SETUP, "0000000");
-- still address 0 here, for as long as the host takes
idle; idle; idle;
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
-- and only now is it address k
idle;
areach(k) := '1';
end loop;
-- PHASE 3 (DIRECTED) -- a bus reset from every state
for si in 0 to 4 loop
goto_state(si);
step('0', x"00", x"0000", '0', '0', '1', '0', TOK_SOF, "0000000");
ck(dev_addr = std_logic_vector'("0000000"),
"a bus reset did not return the device to address 0");
ck(state = std_logic_vector'("000"),
"a bus reset did not return the device to DEFAULT");
ck(configured = '0', "a bus reset left the device configured");
idle;
end loop;
-- PHASE 4 (DIRECTED) -- a status stage with nothing outstanding
for si in 0 to 4 loop
goto_state(si);
for k in 0 to 3 loop
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, dev_addr);
end loop;
end loop;
-- PHASE 4b (DIRECTED, EXHAUSTIVE) -- every request COMPLETED.
--
-- Phase 1 issues a request in every state but never follows it with a
-- status stage, so nothing it does can be observed: two mutations had
-- a directed score of exactly zero. A request that is never completed
-- changes nothing, so a phase that never completes one tests nothing.
for si in 0 to 4 loop
for qi in 0 to 3 loop
goto_state(si);
step('1', reqs(qi), x"0021", '0', '0', '0', '1', TOK_SETUP, dev_addr);
idle;
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, dev_addr);
idle;
-- a second completion, which must change nothing further
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, dev_addr);
idle;
end loop;
end loop;
-- PHASE 4c (DIRECTED, EXHAUSTIVE) -- a REAL enumeration, eight times.
--
-- The chapter's question executed as stimulus: the whole lifecycle from
-- attach to configured, including the two steps that only make sense in
-- sequence -- a SET_CONFIGURATION refused because no address has been
-- established, and a re-address that must UNCONFIGURE the device.
for k in 0 to 7 loop
ea := std_logic_vector(to_unsigned(1 + k*7, 7));
eb := std_logic_vector(to_unsigned(120 - k*7, 7));
reset_dut;
-- 1. SET_CONFIGURATION before any address is established: refused,
-- even though the host completes the transfer.
step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, "0000000");
idle;
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
idle;
ck(configured = '0', "configured without ever having an address");
ck(addressed = '0', "addressed without a completed SET_ADDRESS");
-- 2. GET_DESCRIPTOR at address 0: legal.
step('1', REQ_GET_DESCRIPTOR, x"0000", '0', '0', '0', '1', TOK_SETUP, "0000000");
idle;
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
idle;
ck(dev_addr = std_logic_vector'("0000000"),
"GET_DESCRIPTOR moved the device off address 0");
-- 2b. A SET_ADDRESS that is ABANDONED before its status stage. The
-- device is still NOT addressed while the request is merely
-- outstanding, so the SET_CONFIGURATION is refused. A device that
-- marks itself addressed when the request ARRIVES passes every
-- other check here and fails both of these.
step('1', REQ_SET_ADDRESS, "000000000" & ea, '0', '0', '0', '1', TOK_SETUP, "0000000");
idle;
ck(addressed = '0', "device claimed to be addressed before the status stage");
ck(dev_addr = std_logic_vector'("0000000"), "address changed before the status stage");
step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, "0000000");
idle;
ck(addressed = '0', "device claimed to be addressed before the status stage");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
idle;
ck(configured = '0', "configured off an abandoned SET_ADDRESS");
ck(dev_addr = std_logic_vector'("0000000"), "an abandoned SET_ADDRESS still took effect");
ck(addressed = '0', "addressed off an abandoned SET_ADDRESS");
-- 3. SET_ADDRESS: the old address stays live until the status stage.
step('1', REQ_SET_ADDRESS, "000000000" & ea, '0', '0', '0', '1', TOK_SETUP, "0000000");
idle;
ck(dev_addr = std_logic_vector'("0000000"), "address changed before the status stage");
idle;
ck(dev_addr = std_logic_vector'("0000000"), "address changed before the status stage");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, "0000000");
idle;
ck(dev_addr = ea, "address did not take effect after the status stage");
ck(addressed = '1', "device not addressed after a completed SET_ADDRESS");
-- 4. Now SET_CONFIGURATION is meaningful.
step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, ea);
idle;
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, ea);
idle;
ck(configured = '1', "device did not configure when it should have");
-- 5. Re-address: the configuration survives until the status stage and
-- must be gone immediately after it.
step('1', REQ_SET_ADDRESS, "000000000" & eb, '0', '0', '0', '1', TOK_SETUP, ea);
idle;
ck(dev_addr = ea, "address changed before the status stage");
ck(configured = '1', "configuration dropped before the re-address completed");
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, ea);
idle;
ck(dev_addr = eb, "re-address did not take effect");
ck(configured = '0', "device stayed configured across a re-address");
-- 6. And it can be configured again at the new address.
step('1', REQ_SET_CONFIG, x"0001", '0', '0', '0', '1', TOK_SETUP, eb);
idle;
step('0', x"00", x"0000", '0', '1', '0', '0', TOK_SOF, eb);
idle;
ck(configured = '1', "device could not be reconfigured after a re-address");
end loop;
-- PHASE 5 (RANDOM)
if not DIRECTED_ONLY then
reset_dut;
for k in 0 to 29999 loop
if (nxt mod 4) = 0 then sv3 := '1'; else sv3 := '0'; end if;
if (nxt mod 3) = 0 then ds3 := '1'; else ds3 := '0'; end if;
if (nxt mod 4) = 0 then sa3 := '1'; else sa3 := '0'; end if;
if (nxt mod 200) = 0 then br3 := '1'; else br3 := '0'; end if;
if (nxt mod 2) = 0 then tv3 := '1'; else tv3 := '0'; end if;
if (nxt mod 3) = 0 then
ta3 := std_logic_vector(to_unsigned(nxt mod 128, 7));
else
ta3 := dev_addr;
end if;
step(sv3, reqs(nxt mod 4),
std_logic_vector(to_unsigned(nxt mod 128, 16)),
ds3, sa3, br3, tv3,
std_logic_vector(to_unsigned(nxt mod 4, 2)), ta3);
end loop;
end if;
n_reach := 0;
for i in 0 to 79 loop
if reach(i) = '1' then n_reach := n_reach + 1; end if;
end loop;
n_areach := 0;
for i in 0 to 127 loop
if areach(i) = '1' then n_areach := n_areach + 1; end if;
end loop;
write(ln, string'("steps=") & integer'image(steps)
& string'(" checks=") & integer'image(checks)
& string'(" reach=") & integer'image(n_reach) & string'("/80")
& string'(" addrs=") & integer'image(n_areach) & string'("/128")
& string'(" errors=") & integer'image(errors));
writeline(output, ln);
write(ln, string'("[enum] setups=") & integer'image(x_setup)
& string'(" addr_changes=") & integer'image(x_chg)
& string'(" resets=") & integer'image(x_rst)
& string'(" wrong_addr=") & integer'image(x_wrong)
& string'(" dropped=") & integer'image(x_drop));
writeline(output, ln);
write(ln, string'("[the whole point] premature address switches = ")
& integer'image(n_premature));
writeline(output, ln);
if n_reach /= 80 or n_areach /= 128 then
write(ln, string'("FAIL: exhaustive sweep incomplete"));
writeline(output, ln);
errors := errors + 1;
end if;
if errors = 0 then
write(ln, string'("PASS: 0 errors in ") & integer'image(checks)
& string'(" checks"));
else
write(ln, string'("FAIL: ") & integer'image(errors)
& string'(" errors in ") & integer'image(checks) & string'(" checks"));
end if;
writeline(output, ln);
done <= true;
wait;
end process;
end architecture;10. Exhaustive Verification
| Measure | Verilog | SystemVerilog | VHDL |
|---|---|---|---|
| (state × request × ack × reset) reached | 80 / 80 | 80 / 80 | 80 / 80 |
| addresses swept | 128 / 128 | 128 / 128 | 128 / 128 |
| full enumeration lifecycles | 8 / 8 | 8 / 8 | 8 / 8 |
| Steps | 31648 | 31648 | 31648 |
| Checks executed | 289963 | 289963 | 289900 |
| SETUP packets | 7542 | 7542 | 7468 |
| address changes | 1064 | 1064 | 1023 |
| bus resets | 157 | 157 | 162 |
| tokens for another address | 3719 | 3719 | 3636 |
| requests displaced before completion | 1499 | 1499 | 1517 |
| premature address switches | 0 | 0 | 0 |
| Result | PASS | PASS | PASS |
All 128 addresses matter, not just a representative one. Address 0 is legal — it means "return to the default address" — and address 127 is the maximum; both are the values a design with an off-by-one or a truncated field gets wrong, and both are swept with the full "old address stays live" check around them.
11. Mutation Testing
| # | Mutation | Verilog | SysVer | VHDL |
|---|---|---|---|---|
| C1 | the address is applied when the request arrives | 142863 | 142863 | 142109 |
| C4 | the status stage never retires the request | 90822 | 90822 | 90849 |
| C6 | the address is taken from the wrong bits of wValue | 88237 | 88237 | 88595 |
| C2 | a bus reset does not return the device to address 0 | 65421 | 65421 | 65451 |
| C3 | SET_CONFIGURATION is accepted in the DEFAULT state | 32032 | 32032 | 31754 |
| C7 | the device claims to be addressed when the request arrives | 30765 | 30765 | 29794 |
| C5 | re-addressing leaves the device configured | 10517 | 10517 | 10114 |
| — | unmutated baseline | 0 | 0 | 0 |
All seven die in all three languages, and C1 — the defect this chapter exists for — scores highest of the seven.
Directed against random
| # | V all | V directed | V random | VHDL all | VHDL directed | VHDL random |
|---|---|---|---|---|---|---|
| C1 | 142863 | 4715 | 138148 | 142109 | 4715 | 137394 |
| C2 | 65421 | 1608 | 63813 | 65451 | 1608 | 63843 |
| C3 | 32032 | 144 | 31888 | 31754 | 144 | 31610 |
| C4 | 90822 | 897 | 89925 | 90849 | 897 | 89952 |
| C5 | 10517 | 44 | 10473 | 10114 | 44 | 10070 |
| C6 | 88237 | 2563 | 85674 | 88595 | 2563 | 86032 |
| C7 | 30765 | 76 | 30689 | 29794 | 76 | 29718 |
Every directed column is identical across Verilog and VHDL — 4715, 1608, 144, 897, 44, 2563, 76 — which localises the whole cross-language spread to the random half, where the generators differ by construction. The largest remaining spread is C7 at 3%.
12. Two Mutations Had a Directed Score of Zero
The first decomposition of this chapter read like this:
MUT V-DIR
C3 0 SET_CONFIG accepted in DEFAULT
C5 0 re-address leaves device configured
C7 4 claims addressed too earlyZero is not a small number here, it is a different kind of number: it means the directed phases could not detect the defect at all, and the mutation was being killed entirely by luck in the random phase.
The cause was embarrassing and general. The exhaustive phase drove every request in every state — and never followed any of them with a status stage. A request that is never completed changes nothing, so a phase that never completes one tests nothing. It was 80 scenarios of carefully enumerated stimulus with no observable consequence.
The fix was two phases rather than a bigger sweep:
- Phase 4b completes every request in every state — 5 × 4 scenarios, each followed by a real status stage and then a second one that must change nothing further. C3 went 0 → 88, C5 went 0 → 44.
- Phase 4c runs the chapter's own question as stimulus: eight complete enumerations, each with a different address pair, from attach through refused-configuration, address assignment, configuration, re-address, and re-configuration — with an explicit check at every stage. C3 → 144, C5 → 44, and the address rule itself picked up 848 more kills.
C7 needed one more thing. It is only observable through addressed while a request is outstanding, and nothing checked that. Phase 4c gained an abandoned request — the host asks for an address, changes its mind, and asks for a configuration instead — with addressed checked throughout. C7 went 4 → 76.
13. The Self-Check Was Wrong Twice, and Both Were False Positives
The design carries its own n_early_switch counter, so that the central rule is a published number rather than a claim. Writing it took three attempts and the first two are more instructive than the third.
Attempt 1 — compare the live address to the pending one.
if ((pend_kind == P_ADDR) && (addr_r == pend_addr))
early_c <= early_c + 1;
9607 false positives.
SET_ADDRESS(0) to a device already at address 0 is
LEGAL -- it means "return to the default address" --
and it makes addr_r == pend_addr with nothing wrong.Attempt 2 — remember the address when the request arrived and check it has not moved.
addr_at_req <= addr_r; // on SETUP
...
if ((pend_kind == P_ADDR) && (addr_r != addr_at_req))
early_c <= early_c + 1;
29809 false positives -- WORSE.
A new SET_ADDRESS arriving on the same edge as the
PREVIOUS one's status stage captures the pre-change
address, and the address then legitimately changes.Attempt 3 — capture the address that will be live after this edge.
addr_at_req <= (status_ack && (pend_kind == P_ADDR))
? pend_addr // the change happening NOW
: addr_r;
0 false positives, and the property is exactly right:
while a SET_ADDRESS is outstanding, the live address has
not moved from what it was when the request arrived.14. Follow-Ups the Interviewer Will Ask
"Why does the host read a descriptor at address 0 before assigning an address?" To learn bMaxPacketSize0. It cannot read a descriptor properly without knowing the control endpoint's packet size, so the first read is deliberately minimal and exists only to enable the second.
"What puts the device in the default state?" A port reset — an electrical event driven by the hub at the host's request. Not a message. This is why replugging fixes a wedged device: the reset happens before anything else.
"Is SET_ADDRESS(0) legal?" Yes, and it means "return to the default address." It is also the value that breaks any design that infers "addressed" from a non-zero address, which is mutation C3's territory.
"What happens if the device is re-addressed while configured?" It becomes unconfigured. The Address state is by definition not configured, and the host must configure it again. That is property 6 and mutation C5.
"How long does the device have to switch addresses?" There is a recovery interval after the status stage — 2 ms in USB 2.0 — during which the host will not address it. That window exists precisely because the switch is not instantaneous, and it is the second half of the same rule.
"What if two devices are attached at once?" The host resets and enumerates one port at a time, because both would answer at address 0 simultaneously. This is the only reason address 0 works at all, and it is why enumeration is serialised across a hub.
"Where does this go wrong in practice?" Almost always one of three places: the address applied too early (C1), the descriptor read at the wrong packet size, or a device that does not accept a SETUP while halted — which is chapter 27.1's property 6.
15. UVM: A Scoreboard for a Multi-Stage Transfer
// A control transfer is THREE transactions that mean one thing, and almost
// every enumeration bug lives in the relationship between them rather than
// inside any one. So this scoreboard is built around the stages, and its
// central assertion is about a value that must NOT have changed yet.
typedef enum { STG_SETUP, STG_DATA, STG_STATUS } stage_e;
class ctrl_item extends uvm_sequence_item;
`uvm_object_utils(ctrl_item)
rand stage_e stage;
rand bit [7:0] bRequest;
rand bit [15:0] wValue;
rand bit [6:0] sent_to_addr; // the address the HOST used
rand bit bus_reset;
function new(string name = "ctrl_item"); super.new(name); endfunction
constraint c_reset_is_rare { bus_reset dist { 0 := 199, 1 := 1 }; }
endclass
class enum_scoreboard extends uvm_scoreboard;
`uvm_component_utils(enum_scoreboard)
uvm_analysis_imp #(ctrl_item, enum_scoreboard) ap;
localparam bit [7:0] SET_ADDRESS = 8'h05, SET_CONFIG = 8'h09;
// ---- what has actually COMPLETED ----
bit [6:0] exp_addr;
bit exp_addressed, exp_configured;
// ---- what is merely OUTSTANDING ----
bit pend_addr_valid, pend_conf_valid;
bit [6:0] pend_addr;
int unsigned n_addr_change, n_displaced, n_wrong_addr_used;
int unsigned n_outstanding_cycles; // evidence the window was observed
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
function void write(ctrl_item t);
bit pav, pcv;
bit [6:0] pa;
if (t.bus_reset) begin
// A reset returns the device to DEFAULT from anywhere, and discards
// anything outstanding. Nothing survives it.
exp_addr = 7'd0; exp_addressed = 0; exp_configured = 0;
pend_addr_valid = 0; pend_conf_valid = 0;
return;
end
// ---- THE CENTRAL CHECK ----
//
// While a SET_ADDRESS is outstanding, the host is still talking to the
// OLD address -- so a transaction the host sent to the old address must
// be one the device answered. If the DUT has already moved, the host's
// status stage is unreachable and enumeration cannot complete.
if (pend_addr_valid) begin
n_outstanding_cycles++;
if (t.sent_to_addr != exp_addr)
`uvm_error("USB/ENUM",
$sformatf("host addressed 0x%02h while a SET_ADDRESS to 0x%02h was outstanding: it should still be using 0x%02h",
t.sent_to_addr, pend_addr, exp_addr))
end else begin
if (t.sent_to_addr != exp_addr) n_wrong_addr_used++;
end
// snapshot before this transaction is applied -- the status stage
// resolves what was outstanding BEFORE it, never what arrives with it
pav = pend_addr_valid; pcv = pend_conf_valid; pa = pend_addr;
case (t.stage)
STG_SETUP: begin
if (pav || pcv) n_displaced++; // legal: the host may abandon
pend_addr_valid = 0; pend_conf_valid = 0;
case (t.bRequest)
SET_ADDRESS: begin
pend_addr = t.wValue[6:0];
pend_addr_valid = 1;
// NOT applied. exp_addr is untouched, deliberately.
end
SET_CONFIG: begin
// Gated on a COMPLETED SET_ADDRESS, not on a non-zero address:
// SET_ADDRESS(0) is legal and means "return to default".
pend_conf_valid = exp_addressed;
end
default: ; // GET_DESCRIPTOR and friends leave nothing pending
endcase
end
STG_DATA: ; // moves bytes; changes no state
STG_STATUS: begin
pend_addr_valid = 0; pend_conf_valid = 0;
if (pav) begin
exp_addr = pa; // NOW
exp_addressed = 1;
exp_configured = 0; // re-addressing unconfigures
n_addr_change++;
end else if (pcv) begin
exp_configured = 1;
end
end
endcase
endfunction
function void check_phase(uvm_phase phase);
super.check_phase(phase);
`uvm_info("USB/ENUM",
$sformatf("%0d address changes | %0d displaced requests | %0d outstanding-window transactions",
n_addr_change, n_displaced, n_outstanding_cycles), UVM_LOW)
// The window in which the rule can be broken is SMALL, and a run that
// never spent time in it has not tested the rule however long it ran.
if (n_outstanding_cycles == 0)
`uvm_error("USB/COV",
"no transaction ever occurred while a SET_ADDRESS was outstanding: the central rule was never at risk")
if (n_addr_change == 0)
`uvm_error("USB/COV",
"no SET_ADDRESS ever completed in this run")
if (n_displaced == 0)
`uvm_error("USB/COV",
"no request was ever abandoned before completion: the displacement path was never exercised")
endfunction
endclass16. Common Misconceptions
"SET_ADDRESS takes effect when the device decodes it." After the status stage. This is the whole chapter.
"The device announces itself when plugged in." The hub notices electrically; the host finds out when it next polls the hub.
"Address 0 is invalid." It is the default address every device uses before assignment, and SET_ADDRESS(0) is a legal request meaning "go back to it".
"A device is addressed once the host sends the request." Not until the status stage completes. A device that thinks otherwise accepts a SET_CONFIGURATION it should refuse.
"Re-addressing keeps the configuration." It does not. The Address state is unconfigured by definition.
"Two devices can enumerate at once." Both would answer at address 0. The host enumerates one port at a time.
"The address change is instantaneous." There is a recovery interval — 2 ms in USB 2.0 — during which the host must not address the device.
"A bus reset is a message." It is an electrical condition on the port, which is why it works on a device too confused to parse anything.
"Exhaustive request coverage means the transfers are covered." Two mutations scored 0 directed against an 80/80 sweep, because the sweep never completed a single transfer.
17. Exercises
1. Trace exactly what the host observes if the device applies the address on the SETUP. At which step does enumeration fail, and what does the host's log say?
2. State the general principle behind the rule in one sentence that does not mention USB, then give two examples from other protocols.
3. The design keeps pend_kind separate from have_addr and decodes state from both. Construct the input sequence that drops a request in the single-register version, and say what its failure rate depends on.
4. Attempt 2 of the self-check produced more false positives than attempt 1. Explain why, and give the general lesson about approximating a property.
5. C3 and C5 had directed scores of exactly 0 against a genuinely exhaustive 80/80 sweep. Explain how both can be true, and propose a coverage metric that would have shown the gap.
6. Add the 2 ms recovery interval. Which of the seven properties change, and what new one is needed?
7. SET_ADDRESS(0) is legal. Enumerate everything in the design and the bench that would break if it were not handled, and say which of those breaks would be silent.
18. Summary
| Idea | Why it matters |
|---|---|
SET_ADDRESS applies after the status stage | the acknowledgement must come from the old address |
| A device that switches early is invisible | the host concludes nothing is attached |
| The general rule | a transport-changing request is acked on the old transport |
| The first descriptor read is at address 0 | to learn bMaxPacketSize0 before anything else |
| Attach is detected electrically, reported when polled | a hub is a device and devices do not initiate |
SET_ADDRESS(0) is legal | so never infer "addressed" from a non-zero address |
| A re-address unconfigures | the Address state is unconfigured by definition |
| A bus reset returns to DEFAULT from anywhere | which is why replugging fixes things |
| State ≠ outstanding request | one register for both silently drops requests |
| Status stage decoded before the SETUP | so a same-edge request survives |
| Shadow models need a snapshot, then decide | blocking assignments do not model hardware |
| A checker needs a false-positive test first | 9607 then 29809 false alarms, on a correct design |
| Exhaustive over requests ≠ over transfers | two mutations scored 0 against an 80/80 sweep |
| 80 states, 128 addresses, 7 mutations | 0 premature switches in 289,963 checks |
Tooling
| Step | Command |
|---|---|
| Verilog-2005 | iverilog -g2005 -o en_v.out en_v.v en_v_tb.v && ./en_v.out |
| SystemVerilog | iverilog -g2012 -o en_sv.out en_sv.sv en_sv_tb.sv && ./en_sv.out |
| VHDL-2008 analyse | nvc --std=2008 -a en_vhdl.vhd en_vhdl_tb.vhd |
| VHDL-2008 elaborate | nvc --std=2008 -e tb_en_vhdl |
| VHDL-2008 run | nvc --std=2008 -r tb_en_vhdl |
| One mutation | iverilog -g2005 -DMUT_C1 -o mm en_v_mut.v en_v_tb.v && ./mm |
| Directed only (Verilog) | iverilog -g2005 -DDIRECTED_ONLY -o mm en_v_mut.v en_v_tb.v && ./mm |
| Directed only (VHDL) | nvc --std=2008 -e -gDIRECTED_ONLY=true tb_en_vhdl |
All three implementations pass with 0 errors: all 80 combinations of state, request, status stage and bus reset; all 128 addresses swept with the "old address stays live" check around each; eight complete enumeration lifecycles; zero premature address switches in 289,963 checks; and every one of the seven mutations killed by directed stimulus alone, with all seven directed scores identical across languages.
Chapter 27.4 — The Endpoints Question is the next thing the interviewer reaches for, because enumeration ends with endpoints becoming usable and the obvious follow-up is what they actually are. The answer hinges on a detail the numbering hides: endpoint 1 IN and endpoint 1 OUT are two different endpoints, with separate buffers, separate toggles and separate halt states.
Continue learning
Related tutorials
- Related topic
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.
- Related topic
Downstream Device Discovery
A hub cannot interrupt the host, so every port event waits to be asked for — and the window between the poll and the acknowledgement is where devices are silently lost.
- Related topic
Bus Power
A device's current allowance changes exactly once during enumeration — and bMaxPower is counted in 2 mA units, not milliamps.
- Related topic
Descriptor Engine
wLength is the size of the host's buffer, not a preference — and whether a zero-length packet must follow depends on comparing what was sent against what was asked for, not against what exists.
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.
