USB · Module 30
Compliance Review Checklist
Four different questions get called compliance, and confusing them is how a device that works on every desk fails certification. Separating functional correctness from specification conformance, worked on an exhaustively verified Chapter 9 request-legality table.
A device can pass every test its team wrote, work on every machine in the building, and still be out of conformance with the specification it claims to implement. It can also be perfectly conformant and fail certification on an electrical measurement nobody in the RTL team has ever seen.
This chapter is about not confusing those situations with each other.
1. Four Things Called Compliance
1 TUTORIAL / TEAM REVIEW
Does this design do what we intended?
Settled by: our own tests, our own review.
Proves: nothing about the specification.
2 FUNCTIONAL VERIFICATION
Does the implementation match its specification document?
Settled by: simulation against a model derived from the spec.
Proves: the behaviours we modelled. Nothing about the ones we
did not think to model, and nothing electrical.
3 SPECIFICATION CONFORMANCE
Does the device satisfy every NORMATIVE requirement that applies
to it -- including the ones no host on our desk exercises?
Settled by: a systematic reading of the specification against the
design, plus targeted tests.
Proves: conformance to our reading of the document.
4 OFFICIAL USB-IF COMPLIANCE AND CERTIFICATION
Does the device pass the compliance program -- the published test
suites, on the published equipment, at an authorised event or
lab -- and may it use the logo?
Settled by: that process, and nothing else.
Proves: exactly what the program says it proves.2. The Gap Functional Testing Cannot Close
Every one of these is a real and common shape:
WORKS ON ONE HOST, VIOLATES THE SPECIFICATION
The host never sends the request the device gets wrong. Different
host stacks send different subsets, and the subset your laptop
sends is not the specification.
PASSES PROTOCOL SIMULATION, FAILS ELECTRICAL COMPLIANCE
Eye diagram, rise and fall times, inrush current, droop. RTL has
no opinion about any of it, and a functionally perfect design can
fail on a board layout.
ENUMERATES, THEN VIOLATES CHAPTER 9 LATER
Enumeration exercises a handful of requests in a fixed order. The
requirements that apply AFTER configuration -- what is legal in
which device state -- are not exercised by plugging it in.
CONFORMS, BUT NOT INTEROPERABLY
Conformant behaviour that a popular host stack handles badly.
Real, and it is a business problem rather than a specification one.The third is the one an RTL reviewer owns, and it is the subject of the rest of this chapter. The gap is precise: enumeration is a path through the state machine, and conformance is a property of the whole state machine.
3. The Specimen: A Chapter 9 Legality Table
The question this block answers is narrow and it is a conformance question rather than a functional one:
Of the standard requests the specification defines, which is this device obliged to accept in the state it is currently in, and which must it reject?
A device that answers only the requests its host happens to send will enumerate and work. A device that accepts a request it should have rejected is out of conformance, and nothing visibly breaks — until a host that sends it arrives.
SCOPE USB 2.0 STANDARD device requests only.
NOT IN SCOPE class-specific and vendor-specific requests, which are
defined by their class document rather than by Chapter 9.
The block reports them as "not a standard request" and
declines to have an opinion, which is itself a
conformance decision -- see section 7.// =====================================================================
// usb_req_legal -- does the specification REQUIRE the device to handle
// this standard request in the state it is currently in?
//
// CLASSIFICATION: simplified synthesisable teaching RTL, and it is a
// CONFORMANCE decision rather than a functional one. That distinction
// is the whole reason chapter 30.3 builds it.
//
// A device that answers only the requests its host happens to send
// will enumerate, work, and ship. This block answers a different
// question: of the requests the specification defines, which ones is
// the device OBLIGED to accept right now, and which must it reject?
// A device that accepts a request it should have rejected is out of
// conformance even though nothing visibly breaks -- until a host that
// sends it arrives.
//
// SCOPE: USB 2.0 STANDARD device requests only. Class-specific and
// vendor-specific requests are outside it by construction: they are
// defined by their class document, not by Chapter 9, and this block
// reports them as "not a standard request" rather than guessing.
//
// WHAT THIS BLOCK IS NOT
// It is not a compliance test. It encodes one table from one chapter
// of one specification. Passing it proves that this table was
// implemented; it proves nothing about descriptors, electricals,
// timing, class behaviour or interoperability. See 30.3 section 3.
// =====================================================================
module usb_req_legal (
// ---- the device's current state ----
// 0 DEFAULT powered and reset, no address assigned
// 1 ADDRESS an address has been assigned, not yet configured
// 2 CONFIGURED a non-zero configuration has been selected
input wire [1:0] dev_state,
// ---- the SETUP packet, decoded ----
input wire dir_in, // 1 = device-to-host (a GET)
input wire [1:0] recipient, // 0 DEVICE, 1 INTERFACE, 2 ENDPOINT
input wire [1:0] req_type, // 0 STANDARD, 1 CLASS, 2 VENDOR
input wire [7:0] b_request,
input wire [15:0] w_value,
input wire [15:0] w_index,
input wire [15:0] w_length,
// ---- the device's configuration, for the bounds checks ----
input wire [7:0] n_configs, // how many configurations exist
input wire [7:0] n_interfaces, // how many interfaces the current config has
input wire [3:0] n_endpoints, // highest endpoint number implemented
// ---- the verdict ----
output wire must_handle, // the device is obliged to accept it
output wire must_stall, // the device is obliged to reject it
output wire [3:0] reason
);
localparam [7:0] R_GET_STATUS = 8'd0,
R_CLEAR_FEAT = 8'd1,
R_SET_FEAT = 8'd3,
R_SET_ADDRESS = 8'd5,
R_GET_DESC = 8'd6,
R_SET_DESC = 8'd7,
R_GET_CONFIG = 8'd8,
R_SET_CONFIG = 8'd9,
R_GET_IFACE = 8'd10,
R_SET_IFACE = 8'd11,
R_SYNCH_FRAME = 8'd12;
localparam [1:0] ST_DEFAULT = 2'd0, ST_ADDRESS = 2'd1, ST_CONFIGURED = 2'd2;
localparam [1:0] RCP_DEVICE = 2'd0, RCP_IFACE = 2'd1, RCP_ENDPOINT = 2'd2;
// Reason codes. A verdict without a reason is unreviewable and
// undebuggable: "STALL" on a bus trace tells you nothing about which
// rule the device thought it was applying.
localparam [3:0] OK = 4'd0,
E_NOT_STANDARD = 4'd1, // class or vendor request
E_UNKNOWN_REQ = 4'd2, // not a defined standard request
E_WRONG_STATE = 4'd3, // undefined in this device state
E_BAD_DIR = 4'd4, // data direction contradicts the request
E_BAD_RECIP = 4'd5, // recipient not defined for this request
E_BAD_INDEX = 4'd6, // interface or endpoint does not exist
E_BAD_VALUE = 4'd7, // wValue out of range
E_BAD_LENGTH = 4'd8; // wLength wrong for this request
// ---- is this a standard request at all ----
wire is_standard = (req_type == 2'd0);
wire known_req =
(b_request == R_GET_STATUS) || (b_request == R_CLEAR_FEAT) ||
(b_request == R_SET_FEAT) || (b_request == R_SET_ADDRESS) ||
(b_request == R_GET_DESC) || (b_request == R_SET_DESC) ||
(b_request == R_GET_CONFIG) || (b_request == R_SET_CONFIG) ||
(b_request == R_GET_IFACE) || (b_request == R_SET_IFACE) ||
(b_request == R_SYNCH_FRAME);
// ---- direction: a GET returns data, a SET does not ----
wire want_in =
(b_request == R_GET_STATUS) || (b_request == R_GET_DESC) ||
(b_request == R_GET_CONFIG) || (b_request == R_GET_IFACE) ||
(b_request == R_SYNCH_FRAME);
wire dir_ok = (dir_in == want_in);
// ---- recipient: which recipients each request is defined for ----
wire recip_ok =
(b_request == R_GET_STATUS) ? (recipient <= RCP_ENDPOINT) :
(b_request == R_CLEAR_FEAT) ? (recipient <= RCP_ENDPOINT) :
(b_request == R_SET_FEAT) ? (recipient <= RCP_ENDPOINT) :
(b_request == R_GET_IFACE) ? (recipient == RCP_IFACE) :
(b_request == R_SET_IFACE) ? (recipient == RCP_IFACE) :
(b_request == R_SYNCH_FRAME) ? (recipient == RCP_ENDPOINT) :
(recipient == RCP_DEVICE);
// ---- state: where each request is DEFINED ----
// In the Default state only three requests are defined. Everything
// else is undefined behaviour, and "undefined" is not "harmless":
// a device that answers GET_CONFIGURATION before it has an address
// is reporting a configuration it cannot have.
wire def_ok =
(b_request == R_SET_ADDRESS) || (b_request == R_GET_DESC) ||
(b_request == R_SET_DESC);
// In the Address state the interface- and endpoint-directed forms
// are defined only for interface zero and endpoint zero, because no
// others exist until a configuration is selected.
wire addr_ok =
(b_request != R_GET_IFACE) && (b_request != R_SET_IFACE) &&
(b_request != R_SYNCH_FRAME);
wire state_ok =
(dev_state == ST_DEFAULT) ? def_ok :
(dev_state == ST_ADDRESS) ? addr_ok :
(dev_state == ST_CONFIGURED) ? 1'b1 : 1'b0;
// ---- index and value bounds ----
wire iface_exists =
(dev_state == ST_CONFIGURED) ? (w_index[7:0] < n_interfaces)
: (w_index[7:0] == 8'd0);
wire ep_exists =
(dev_state == ST_CONFIGURED) ? (w_index[3:0] <= n_endpoints)
: (w_index[3:0] == 4'd0);
wire index_ok =
(recipient == RCP_IFACE) ? iface_exists :
(recipient == RCP_ENDPOINT) ? ep_exists : 1'b1;
wire value_ok =
(b_request == R_SET_ADDRESS) ? (w_value <= 16'd127) :
(b_request == R_SET_CONFIG) ? (w_value[7:0] <= n_configs) : 1'b1;
// ---- length: the no-data-stage requests must ask for no data ----
wire zero_len_req =
(b_request == R_CLEAR_FEAT) || (b_request == R_SET_FEAT) ||
(b_request == R_SET_ADDRESS) || (b_request == R_SET_CONFIG) ||
(b_request == R_SET_IFACE);
wire length_ok = zero_len_req ? (w_length == 16'd0) : 1'b1;
// ---- the verdict, in the order a reviewer would apply the rules ----
assign reason =
!is_standard ? E_NOT_STANDARD :
!known_req ? E_UNKNOWN_REQ :
!dir_ok ? E_BAD_DIR :
!recip_ok ? E_BAD_RECIP :
!state_ok ? E_WRONG_STATE :
!index_ok ? E_BAD_INDEX :
!value_ok ? E_BAD_VALUE :
!length_ok ? E_BAD_LENGTH : OK;
assign must_handle = (reason == OK);
// A class or vendor request is NOT this block's to reject. It reports
// the fact and leaves the decision to whatever implements that class:
// a device with no class handler stalls it, and a device with one
// does not, and neither is a Chapter 9 question.
assign must_stall = (reason != OK) && (reason != E_NOT_STANDARD);
endmodule4. Question — Is This Request DEFINED In This State?
INVARIANT a request is accepted only in the device states
where the specification defines it; in every other
state the device rejects it
EVIDENCE the state/request table, entry by entry, against
the specification, with the "undefined" entries
treated as REJECT rather than as don't-care
FAILURE SIGNATURE a device that answers a question it should not have
understood, returning a value it cannot have
FALSE CONFIDENCE "it enumerates" -- enumeration is one path
NEXT ask what the device would RETURN, and whether that
value exists yetThe table this specimen encodes, in the three device states that matter:
REQUEST DEFAULT ADDRESS CONFIGURED
------------------- ----------- ------------ -----------
GET_STATUS undefined yes (if 0) yes
CLEAR_FEATURE undefined yes (if 0) yes
SET_FEATURE undefined yes (if 0) yes
SET_ADDRESS yes yes yes
GET_DESCRIPTOR yes yes yes
SET_DESCRIPTOR yes yes yes
GET_CONFIGURATION undefined yes yes
SET_CONFIGURATION undefined yes yes
GET_INTERFACE undefined undefined yes
SET_INTERFACE undefined undefined yes
SYNCH_FRAME undefined undefined yes
"if 0" in the Address state the interface- and endpoint-directed
forms apply only to interface zero and endpoint zero,
because no others exist until a configuration is selected.5. Question — Are The Field Bounds Right?
INVARIANT every field in the request has a legal range, and
the design enforces the range the specification
states rather than the range the field is wide
enough to hold
EVIDENCE each field, its width, its legal range, and the
line that enforces it
FAILURE SIGNATURE accepting a value that cannot mean anything, then
acting on it
FALSE CONFIDENCE "the field is 16 bits so any 16-bit value is fine"
NEXT ask what the device does with the out-of-range
value it just acceptedTwo in this specimen are worth the ink:
SET_ADDRESS wValue carries the address. The address FIELD on the bus
is seven bits, so the legal range is 0..127 even though
wValue is sixteen bits wide. Mutation B-M2 accepts up to
255; caught by three directed checks, and by no host,
because no host sends 200.
SET_CONFIG wValue selects a configuration. Zero is legal and means
"return to the Address state" -- a value that is easy to
read as an error and reject, which breaks a host that is
deliberately unconfiguring the device.The second is the better lesson: a bounds check that is too strict is also a conformance defect, and it is the one review misses because rejecting things feels safe.
6. Question — Does The Data Stage Match The Request?
A GET returns data; a SET does not. The direction bit in the request type
must agree with the request, and a mismatch is a malformed request rather than
an unusual one.
Mutation B-M3 removes the direction check entirely. It scores 167
directed failures — by far the largest in this specimen — because the exhaustive
sweep drives both directions against every request, so every GET with the wrong
direction bit and every SET with the wrong one is a separate failure.
That number is worth contrasting with B-M1's three. Both are conformance defects. One is caught 167 times because the sweep has direction as an axis; the other is caught three times because it is one cell of one table. Mutation scores measure the shape of the stimulus at least as much as the severity of the defect, and a reviewer who ranks findings by mutation score has ranked them by how easy they were to test.
7. Question — Is This Ours To Reject?
INVARIANT the block decides only the questions its scope
covers, and says so for the rest
EVIDENCE what the design does with a request type it does
not implement
FAILURE SIGNATURE a class request rejected by the Chapter 9 layer,
so the class driver never sees it and the feature
silently does not exist
FALSE CONFIDENCE "we reject what we do not recognise" -- safe for
standard requests, wrong for class ones
NEXT ask who DOES decide, and whether they exist yetThe specimen returns a distinct verdict for a class or vendor request: not
must_handle, and explicitly not must_stall either.
assign must_stall = (reason != OK) && (reason != E_NOT_STANDARD);A device with no handler for that class stalls it; a device with one does not; and neither is a Chapter 9 question. Mutation B-M5 collapses the distinction and stalls everything non-standard. It scores one directed failure — the single scenario written for it — which makes it the thinnest score in the module and a good illustration of why thin scores need attention rather than celebration.
8. SystemVerilog And VHDL
// =====================================================================
// usb_req_legal_sv -- the same Chapter 9 legality table, in
// SystemVerilog. Identical contract to usb_req_legal.v: same ports,
// same rule order, same verdicts, same reason codes.
//
// A decision table is exactly where a REVIEWER wants the language to
// help, and here it does two things the Verilog version cannot.
//
// The request codes are an ENUM, so a table entry for a code that is
// not a standard request is a compile error rather than a silent
// branch nobody will ever take.
//
// The per-request attributes are looked up in ONE unique case, so the
// tool reports an overlapping or missing entry. In the Verilog version
// the same information is spread across five separate conditional
// cascades -- five places to forget the same request, and no tool that
// will say so. That is a review finding waiting to happen, and it is a
// fair criticism of the Verilog version rather than of Verilog.
// =====================================================================
module usb_req_legal_sv (
input logic [1:0] dev_state,
input logic dir_in,
input logic [1:0] recipient,
input logic [1:0] req_type,
input logic [7:0] b_request,
input logic [15:0] w_value,
input logic [15:0] w_index,
input logic [15:0] w_length,
input logic [7:0] n_configs,
input logic [7:0] n_interfaces,
input logic [3:0] n_endpoints,
output logic must_handle,
output logic must_stall,
output logic [3:0] reason
);
typedef enum logic [7:0] {
REQ_GET_STATUS = 8'd0,
REQ_CLEAR_FEAT = 8'd1,
REQ_SET_FEAT = 8'd3,
REQ_SET_ADDRESS = 8'd5,
REQ_GET_DESC = 8'd6,
REQ_SET_DESC = 8'd7,
REQ_GET_CONFIG = 8'd8,
REQ_SET_CONFIG = 8'd9,
REQ_GET_IFACE = 8'd10,
REQ_SET_IFACE = 8'd11,
REQ_SYNCH_FRAME = 8'd12
} req_e;
typedef enum logic [1:0] {
ST_DEFAULT = 2'd0, ST_ADDRESS = 2'd1, ST_CONFIGURED = 2'd2
} state_e;
typedef enum logic [1:0] {
RCP_DEVICE = 2'd0, RCP_IFACE = 2'd1, RCP_ENDPOINT = 2'd2
} recip_e;
localparam logic [3:0] OK = 4'd0, E_NOT_STANDARD = 4'd1,
E_UNKNOWN_REQ = 4'd2, E_WRONG_STATE = 4'd3,
E_BAD_DIR = 4'd4, E_BAD_RECIP = 4'd5,
E_BAD_INDEX = 4'd6, E_BAD_VALUE = 4'd7,
E_BAD_LENGTH = 4'd8;
// ---- ONE lookup, so there is one place to forget a request ----
logic known, wants_in, zero_len;
logic [2:0] recip_mask; // bit0 DEVICE, bit1 INTERFACE, bit2 ENDPOINT
logic [2:0] state_mask; // bit0 DEFAULT, bit1 ADDRESS, bit2 CONFIGURED
always_comb begin
known = 1'b1; wants_in = 1'b0; zero_len = 1'b0;
recip_mask = 3'b001; state_mask = 3'b110;
unique case (b_request)
REQ_GET_STATUS: begin wants_in=1'b1; recip_mask=3'b111; state_mask=3'b110; end
REQ_CLEAR_FEAT: begin zero_len=1'b1; recip_mask=3'b111; state_mask=3'b110; end
REQ_SET_FEAT: begin zero_len=1'b1; recip_mask=3'b111; state_mask=3'b110; end
REQ_SET_ADDRESS: begin zero_len=1'b1; recip_mask=3'b001; state_mask=3'b111; end
REQ_GET_DESC: begin wants_in=1'b1; recip_mask=3'b001; state_mask=3'b111; end
REQ_SET_DESC: begin recip_mask=3'b001; state_mask=3'b111; end
REQ_GET_CONFIG: begin wants_in=1'b1; recip_mask=3'b001; state_mask=3'b110; end
REQ_SET_CONFIG: begin zero_len=1'b1; recip_mask=3'b001; state_mask=3'b110; end
REQ_GET_IFACE: begin wants_in=1'b1; recip_mask=3'b010; state_mask=3'b100; end
REQ_SET_IFACE: begin zero_len=1'b1; recip_mask=3'b010; state_mask=3'b100; end
REQ_SYNCH_FRAME: begin wants_in=1'b1; recip_mask=3'b100; state_mask=3'b100; end
default: known = 1'b0;
endcase
end
logic iface_exists, ep_exists, index_ok, value_ok, length_ok;
assign iface_exists = (dev_state == ST_CONFIGURED)
? (w_index[7:0] < n_interfaces)
: (w_index[7:0] == 8'd0);
assign ep_exists = (dev_state == ST_CONFIGURED)
? (w_index[3:0] <= n_endpoints)
: (w_index[3:0] == 4'd0);
assign index_ok = (recipient == RCP_IFACE) ? iface_exists :
(recipient == RCP_ENDPOINT) ? ep_exists : 1'b1;
assign value_ok = (b_request == REQ_SET_ADDRESS) ? (w_value <= 16'd127) :
(b_request == REQ_SET_CONFIG) ? (w_value[7:0] <= n_configs)
: 1'b1;
assign length_ok = zero_len ? (w_length == 16'd0) : 1'b1;
logic recip_ok, state_ok;
assign recip_ok = (recipient <= RCP_ENDPOINT) && recip_mask[recipient];
assign state_ok = (dev_state <= ST_CONFIGURED) && state_mask[dev_state];
always_comb begin
if (req_type != 2'd0) reason = E_NOT_STANDARD;
else if (!known) reason = E_UNKNOWN_REQ;
else if (dir_in != wants_in) reason = E_BAD_DIR;
else if (!recip_ok) reason = E_BAD_RECIP;
else if (!state_ok) reason = E_WRONG_STATE;
else if (!index_ok) reason = E_BAD_INDEX;
else if (!value_ok) reason = E_BAD_VALUE;
else if (!length_ok) reason = E_BAD_LENGTH;
else reason = OK;
end
assign must_handle = (reason == OK);
assign must_stall = (reason != OK) && (reason != E_NOT_STANDARD);
endmodule-- =====================================================================
-- usb_req_legal (VHDL-2008) -- the same Chapter 9 legality table.
-- Identical contract to the Verilog and SystemVerilog versions.
--
-- A decision table is where VHDL's typing earns the most in review.
-- The request code arrives as a vector, is decoded ONCE into an
-- enumerated type, and every table afterwards is a case over that
-- type -- which the compiler checks for completeness. A forgotten
-- request is a compile error here, a silent default in Verilog, and a
-- tool warning in SystemVerilog.
--
-- The `others` branch on the DECODE is the only place a vector is
-- switched on, and it is the honest one: the eight-bit field really
-- does have 245 values that are not standard requests.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity usb_req_legal is
port (
dev_state : in unsigned(1 downto 0);
dir_in : in std_logic;
recipient : in unsigned(1 downto 0);
req_type : in unsigned(1 downto 0);
b_request : in unsigned(7 downto 0);
w_value : in unsigned(15 downto 0);
w_index : in unsigned(15 downto 0);
w_length : in unsigned(15 downto 0);
n_configs : in unsigned(7 downto 0);
n_interfaces : in unsigned(7 downto 0);
n_endpoints : in unsigned(3 downto 0);
must_handle : out std_logic;
must_stall : out std_logic;
reason : out unsigned(3 downto 0)
);
end entity usb_req_legal;
architecture rtl of usb_req_legal is
type req_t is (
REQ_GET_STATUS, REQ_CLEAR_FEAT, REQ_SET_FEAT, REQ_SET_ADDRESS,
REQ_GET_DESC, REQ_SET_DESC, REQ_GET_CONFIG, REQ_SET_CONFIG,
REQ_GET_IFACE, REQ_SET_IFACE, REQ_SYNCH_FRAME, REQ_UNKNOWN
);
-- One record per request, so the eleven facts about a request live in
-- one row rather than in five separate cascades.
type req_attr_t is record
wants_in : std_logic;
zero_len : std_logic;
recip_mask : std_logic_vector(2 downto 0); -- DEVICE / IFACE / ENDPOINT
state_mask : std_logic_vector(2 downto 0); -- DEFAULT / ADDRESS / CONFIGURED
end record req_attr_t;
signal req : req_t;
signal attr : req_attr_t;
signal known : std_logic;
signal idx_ok, val_ok, len_ok, recip_ok, state_ok : std_logic;
signal rsn : unsigned(3 downto 0);
constant OK_C : unsigned(3 downto 0) := to_unsigned(0, 4);
constant E_NOT_STANDARD_C : unsigned(3 downto 0) := to_unsigned(1, 4);
constant E_UNKNOWN_REQ_C : unsigned(3 downto 0) := to_unsigned(2, 4);
constant E_WRONG_STATE_C : unsigned(3 downto 0) := to_unsigned(3, 4);
constant E_BAD_DIR_C : unsigned(3 downto 0) := to_unsigned(4, 4);
constant E_BAD_RECIP_C : unsigned(3 downto 0) := to_unsigned(5, 4);
constant E_BAD_INDEX_C : unsigned(3 downto 0) := to_unsigned(6, 4);
constant E_BAD_VALUE_C : unsigned(3 downto 0) := to_unsigned(7, 4);
constant E_BAD_LENGTH_C : unsigned(3 downto 0) := to_unsigned(8, 4);
begin
-- The ONE place a vector is switched on, and its `others` is real.
decode : process (b_request)
begin
case to_integer(b_request) is
when 0 => req <= REQ_GET_STATUS;
when 1 => req <= REQ_CLEAR_FEAT;
when 3 => req <= REQ_SET_FEAT;
when 5 => req <= REQ_SET_ADDRESS;
when 6 => req <= REQ_GET_DESC;
when 7 => req <= REQ_SET_DESC;
when 8 => req <= REQ_GET_CONFIG;
when 9 => req <= REQ_SET_CONFIG;
when 10 => req <= REQ_GET_IFACE;
when 11 => req <= REQ_SET_IFACE;
when 12 => req <= REQ_SYNCH_FRAME;
when others => req <= REQ_UNKNOWN;
end case;
end process decode;
-- A case over an ENUMERATED type. Remove a branch and it will not
-- compile, which is the property the other two languages lack.
attrs : process (req)
begin
case req is
when REQ_GET_STATUS => attr <= ('1','0',"111","110");
when REQ_CLEAR_FEAT => attr <= ('0','1',"111","110");
when REQ_SET_FEAT => attr <= ('0','1',"111","110");
when REQ_SET_ADDRESS => attr <= ('0','1',"001","111");
when REQ_GET_DESC => attr <= ('1','0',"001","111");
when REQ_SET_DESC => attr <= ('0','0',"001","111");
when REQ_GET_CONFIG => attr <= ('1','0',"001","110");
when REQ_SET_CONFIG => attr <= ('0','1',"001","110");
when REQ_GET_IFACE => attr <= ('1','0',"010","100");
when REQ_SET_IFACE => attr <= ('0','1',"010","100");
when REQ_SYNCH_FRAME => attr <= ('1','0',"100","100");
when REQ_UNKNOWN => attr <= ('0','0',"000","000");
end case;
end process attrs;
known <= '0' when req = REQ_UNKNOWN else '1';
idx_ok <= '1' when recipient = 1 and dev_state = 2 and
w_index(7 downto 0) < n_interfaces else
'1' when recipient = 1 and dev_state /= 2 and
w_index(7 downto 0) = 0 else
'1' when recipient = 2 and dev_state = 2 and
w_index(3 downto 0) <= n_endpoints else
'1' when recipient = 2 and dev_state /= 2 and
w_index(3 downto 0) = 0 else
'1' when recipient = 0 else
'0';
val_ok <= '0' when req = REQ_SET_ADDRESS and w_value > 127 else
'0' when req = REQ_SET_CONFIG and w_value(7 downto 0) > n_configs
else '1';
len_ok <= '0' when attr.zero_len = '1' and w_length /= 0 else '1';
recip_ok <= '0' when recipient > 2 else attr.recip_mask(to_integer(recipient));
state_ok <= '0' when dev_state > 2 else attr.state_mask(to_integer(dev_state));
rsn <= E_NOT_STANDARD_C when req_type /= 0 else
E_UNKNOWN_REQ_C when known = '0' else
E_BAD_DIR_C when dir_in /= attr.wants_in else
E_BAD_RECIP_C when recip_ok = '0' else
E_WRONG_STATE_C when state_ok = '0' else
E_BAD_INDEX_C when idx_ok = '0' else
E_BAD_VALUE_C when val_ok = '0' else
E_BAD_LENGTH_C when len_ok = '0' else
OK_C;
reason <= rsn;
must_handle <= '1' when rsn = OK_C else '0';
must_stall <= '1' when (rsn /= OK_C and rsn /= E_NOT_STANDARD_C) else '0';
end architecture rtl;9. The Exhaustive Sweep, And Its Denominator
A legality table is one of the rare things in hardware that can be checked completely, and when something can be exhausted the review item is to make sure the denominator is honest.
device states DEFAULT, ADDRESS, CONFIGURED 3
recipients DEVICE, INTERFACE, ENDPOINT 3
request codes 0..15: the eleven standard requests, plus five
undefined ones -- which is the case the
specification is most often got wrong about 16
directions device-to-host, host-to-device 2
------------------------------------------------------------------
3 x 3 x 16 x 2 = 288and the exclusions, named:
recipient 3 is reserved, and a sweep including it would measure the
reserved encoding rather than the table
wValue, wIndex and wLength are held at LEGAL values in this phase so
that the state, recipient and direction rules are what is being
measured. Their bounds are a separate phase, one scenario each.288 verdicts, three checks each, all compared against a reference table written independently of the design's structure. The results:
VERILOG SYSTEMVERILOG VHDL-2008
phase 1 exhaustive 864 864 864
phase 2 bounds 903 903 903
---- DIRECTED 903 903 903
errors 0 0 0
TOTAL 60,903 60,903 60,903and, more usefully than the totals, the verdict classes the sweep actually reached:
must handle ............ 1,018
must stall ............. 17,246
undefined in state ... 1,038
wrong direction ...... 6,312
wrong recipient ...... 3,014
index out of range ... 665
value out of range ... 200
length wrong ......... 212
not a defined request 5,805
not a standard request . 2,037Every reason code is reached. That is the reachability item from 30.2 applied to a decision table: a verdict class with a count of zero is a rule that has never been exercised, and in a conformance table that is precisely the rule that will be wrong.
10. Mutation As Conformance Evidence
MUT REVIEW QUESTION V-DIR SV-DIR VH-DIR
B-M1 is this request DEFINED in this state? 3 3 3
B-M2 is the address field really seven bits? 3 3 3
B-M3 does the data stage match the request? 167 167 167
B-M4 does endpoint N exist? 3 3 3
B-M5 is a class request Chapter 9's to reject? 1 1 1BASE reads zero in all nine columns and every directed column is identical
across the three languages.
Four of the five score one or three. That is not weakness; it is the arithmetic of a table. A single wrong cell is wrong in exactly the situations that reach that cell, and a sweep visits each cell once. A conformance defect is intrinsically low-yield to test and high-consequence to ship, which is the opposite of most RTL defects and is the reason conformance gets reviewed rather than fuzzed.
The practical consequence for a reviewer: in a conformance table, do not use mutation scores to rank findings. Use them to confirm that each cell is reached at all.
11. The Testbench
// =====================================================================
// tb_usb_req_legal -- Verilog-2005 testbench for usb_req_legal.
//
// The reference model here is written as a TABLE, not as the same
// cascade of conditions the design uses. That is deliberate and it is
// the review question from 30.2 applied to this block: a checker built
// the same way as the design reproduces the design's misreading of the
// specification along with it, and then agrees with it.
//
// PHASES
// 1 EXHAUSTIVE 3 states x 3 recipients x 16 request codes x 2
// directions = 288 verdicts, every one compared
// 2 BOUNDARY the value, index and length limits, one each
// 3 RANDOM supplementary, over the whole field space
// =====================================================================
`timescale 1ns/1ps
module tb_usb_req_legal;
reg [1:0] dev_state;
reg dir_in;
reg [1:0] recipient, req_type;
reg [7:0] b_request;
reg [15:0] w_value, w_index, w_length;
reg [7:0] n_configs, n_interfaces;
reg [3:0] n_endpoints;
wire must_handle, must_stall;
wire [3:0] reason;
usb_req_legal dut (
.dev_state(dev_state), .dir_in(dir_in), .recipient(recipient),
.req_type(req_type), .b_request(b_request),
.w_value(w_value), .w_index(w_index), .w_length(w_length),
.n_configs(n_configs), .n_interfaces(n_interfaces),
.n_endpoints(n_endpoints),
.must_handle(must_handle), .must_stall(must_stall), .reason(reason)
);
integer chk_dir, chk_rnd, err, in_random;
integer m_handle, m_stall, m_notstd, m_state, m_dir, m_recip,
m_index, m_value, m_length, m_unknown;
integer st, rc, rq, di, i;
task bump; begin
if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
end endtask
// -----------------------------------------------------------------
// The reference model: a table lookup, structurally unlike the DUT.
// ref_reason returns the code the specification requires.
// -----------------------------------------------------------------
function [3:0] ref_reason;
input [1:0] st_i;
input dir_i;
input [1:0] rcp_i;
input [1:0] typ_i;
input [7:0] req_i;
input [15:0] val_i, idx_i, len_i;
input [7:0] ncfg, nif;
input [3:0] nep;
// per-request attributes, looked up rather than recomputed
reg known, wants_in, zero_len;
reg [2:0] recip_mask; // bit0 DEVICE, bit1 INTERFACE, bit2 ENDPOINT
reg [2:0] state_mask; // bit0 DEFAULT, bit1 ADDRESS, bit2 CONFIGURED
reg idx_ok, val_ok;
begin
known = 1'b1; wants_in = 1'b0; zero_len = 1'b0;
recip_mask = 3'b001; state_mask = 3'b110;
case (req_i)
8'd0: begin wants_in=1; recip_mask=3'b111; state_mask=3'b110; end // GET_STATUS
8'd1: begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // CLEAR_FEATURE
8'd3: begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // SET_FEATURE
8'd5: begin zero_len=1; recip_mask=3'b001; state_mask=3'b111; end // SET_ADDRESS
8'd6: begin wants_in=1; recip_mask=3'b001; state_mask=3'b111; end // GET_DESCRIPTOR
8'd7: begin recip_mask=3'b001; state_mask=3'b111; end // SET_DESCRIPTOR
8'd8: begin wants_in=1; recip_mask=3'b001; state_mask=3'b110; end // GET_CONFIGURATION
8'd9: begin zero_len=1; recip_mask=3'b001; state_mask=3'b110; end // SET_CONFIGURATION
8'd10: begin wants_in=1; recip_mask=3'b010; state_mask=3'b100; end // GET_INTERFACE
8'd11: begin zero_len=1; recip_mask=3'b010; state_mask=3'b100; end // SET_INTERFACE
8'd12: begin wants_in=1; recip_mask=3'b100; state_mask=3'b100; end // SYNCH_FRAME
default: known = 1'b0;
endcase
if (rcp_i == 2'd1)
idx_ok = (st_i == 2'd2) ? (idx_i[7:0] < nif) : (idx_i[7:0] == 8'd0);
else if (rcp_i == 2'd2)
idx_ok = (st_i == 2'd2) ? (idx_i[3:0] <= nep) : (idx_i[3:0] == 4'd0);
else
idx_ok = 1'b1;
if (req_i == 8'd5) val_ok = (val_i <= 16'd127);
else if (req_i == 8'd9) val_ok = (val_i[7:0] <= ncfg);
else val_ok = 1'b1;
if (typ_i != 2'd0) ref_reason = 4'd1;
else if (!known) ref_reason = 4'd2;
else if (dir_i !== wants_in) ref_reason = 4'd4;
else if (!recip_mask[rcp_i] || rcp_i == 2'd3) ref_reason = 4'd5;
else if (!state_mask[st_i]) ref_reason = 4'd3;
else if (!idx_ok) ref_reason = 4'd6;
else if (!val_ok) ref_reason = 4'd7;
else if (zero_len && (len_i != 16'd0)) ref_reason = 4'd8;
else ref_reason = 4'd0;
end
endfunction
task check_now;
input [255:0] what;
reg [3:0] exp_r;
reg exp_h, exp_s;
begin
#1;
exp_r = ref_reason(dev_state, dir_in, recipient, req_type, b_request,
w_value, w_index, w_length,
n_configs, n_interfaces, n_endpoints);
exp_h = (exp_r == 4'd0);
exp_s = (exp_r != 4'd0) && (exp_r != 4'd1);
bump;
if (reason !== exp_r) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %0s reason: got %0d expected %0d (state=%0d rcp=%0d req=%0d dir=%0d)",
what, reason, exp_r, dev_state, recipient, b_request, dir_in);
end
bump;
if (must_handle !== exp_h) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %0s must_handle: got %0d expected %0d", what, must_handle, exp_h);
end
bump;
if (must_stall !== exp_s) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %0s must_stall: got %0d expected %0d", what, must_stall, exp_s);
end
// tallies -- what this sweep actually reached
case (exp_r)
4'd0: m_handle = m_handle + 1;
4'd1: m_notstd = m_notstd + 1;
4'd2: m_unknown = m_unknown + 1;
4'd3: m_state = m_state + 1;
4'd4: m_dir = m_dir + 1;
4'd5: m_recip = m_recip + 1;
4'd6: m_index = m_index + 1;
4'd7: m_value = m_value + 1;
4'd8: m_length = m_length + 1;
endcase
if (exp_s) m_stall = m_stall + 1;
end
endtask
// -----------------------------------------------------------------
// PHASE 1 -- the exhaustive sweep.
//
// DENOMINATOR, derived:
// device states DEFAULT, ADDRESS, CONFIGURED 3
// recipients DEVICE, INTERFACE, ENDPOINT 3
// request codes 0..15: the eleven standard requests plus
// five undefined ones, which is the case the
// specification is most often wrong about 16
// directions device-to-host, host-to-device 2
// ------------------------------------------------------------------
// 3 x 3 x 16 x 2 = 288
//
// Recipient 3 is excluded: it is reserved, and a sweep that included
// it would be measuring the reserved encoding rather than the table.
// wValue, wIndex and wLength are held at LEGAL values here so that
// the state/recipient/direction rules are what is being measured;
// their bounds are phase 2, one scenario each.
// -----------------------------------------------------------------
task phase_sweep;
begin
req_type = 2'd0;
n_configs = 8'd2;
n_interfaces = 8'd3;
n_endpoints = 4'd4;
w_value = 16'd0;
w_index = 16'd0;
w_length = 16'd0;
for (st = 0; st < 3; st = st + 1)
for (rc = 0; rc < 3; rc = rc + 1)
for (rq = 0; rq < 16; rq = rq + 1)
for (di = 0; di < 2; di = di + 1) begin
dev_state = st[1:0];
recipient = rc[1:0];
b_request = rq[7:0];
dir_in = di[0];
// GET_DESCRIPTOR and GET_STATUS legitimately carry a length
w_length = (rq == 0 || rq == 6 || rq == 8 || rq == 10 || rq == 12)
? 16'd8 : 16'd0;
check_now("sweep");
end
end
endtask
// -----------------------------------------------------------------
// PHASE 2 -- the bounds, one scenario each.
// -----------------------------------------------------------------
task phase_bounds;
begin
req_type = 2'd0; n_configs = 8'd2; n_interfaces = 8'd3; n_endpoints = 4'd4;
// SET_ADDRESS: the address field is seven bits wide
dev_state=2'd1; recipient=2'd0; b_request=8'd5; dir_in=1'b0;
w_index=0; w_length=0;
w_value = 16'd127; check_now("addr 127 legal");
w_value = 16'd128; check_now("addr 128 illegal");
// SET_CONFIGURATION: zero is legal and means "unconfigure"
dev_state=2'd2; recipient=2'd0; b_request=8'd9; dir_in=1'b0;
w_value = 16'd0; check_now("config 0 legal");
w_value = 16'd2; check_now("config 2 legal");
w_value = 16'd3; check_now("config 3 illegal");
// a no-data-stage request that asks for data
w_value = 16'd1; w_length = 16'd8; check_now("set-config with length");
w_length = 16'd0;
// interface index bounds, and the Address-state special case
dev_state=2'd2; recipient=2'd1; b_request=8'd10; dir_in=1'b1;
w_length=16'd1;
w_index = 16'd2; check_now("iface 2 exists");
w_index = 16'd3; check_now("iface 3 does not");
dev_state=2'd1; recipient=2'd1; b_request=8'd0; dir_in=1'b1;
w_length=16'd2;
w_index = 16'd0; check_now("iface 0 in address state");
w_index = 16'd1; check_now("iface 1 in address state");
// endpoint index bounds
dev_state=2'd2; recipient=2'd2; b_request=8'd0; dir_in=1'b1;
w_length=16'd2;
w_index = 16'd4; check_now("ep 4 exists");
w_index = 16'd5; check_now("ep 5 does not");
// a class request is not this block's to reject
dev_state=2'd2; recipient=2'd1; req_type=2'd1; b_request=8'd10;
dir_in=1'b1; w_index=16'd0; w_length=16'd1;
check_now("class request passes through");
req_type = 2'd0;
end
endtask
// -----------------------------------------------------------------
// PHASE 3 -- random over the whole field space.
// -----------------------------------------------------------------
task phase_random;
begin
in_random = 1;
for (i = 0; i < 20000; i = i + 1) begin
dev_state = ({$random} % 3);
recipient = ({$random} % 3);
req_type = (({$random} % 100) < 85) ? 2'd0 : ({$random} % 3);
b_request = (({$random} % 100) < 80) ? ({$random} % 13)
: ({$random} % 256);
dir_in = ({$random} % 2);
w_value = (({$random} % 100) < 60) ? ({$random} % 132)
: ({$random} % 65536);
w_index = (({$random} % 100) < 70) ? ({$random} % 8)
: ({$random} % 65536);
w_length = (({$random} % 100) < 50) ? 16'd0 : ({$random} % 64);
n_configs = ({$random} % 4) + 8'd1;
n_interfaces = ({$random} % 4) + 8'd1;
n_endpoints = ({$random} % 8);
check_now("random");
end
in_random = 0;
end
endtask
initial begin
chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
m_handle=0; m_stall=0; m_notstd=0; m_state=0; m_dir=0; m_recip=0;
m_index=0; m_value=0; m_length=0; m_unknown=0;
phase_sweep;
$display(" phase 1 exhaustive : %0d checks, %0d errors (288 verdicts)",
chk_dir, err);
phase_bounds;
$display(" phase 2 bounds : %0d checks, %0d errors", chk_dir, err);
$display(" ---- DIRECTED-ONLY : %0d checks, %0d errors ----", chk_dir, err);
phase_random;
$display("");
$display(" verdicts reached (all phases)");
$display(" must handle ............ %0d", m_handle);
$display(" must stall ............. %0d", m_stall);
$display(" undefined in state ... %0d", m_state);
$display(" wrong direction ...... %0d", m_dir);
$display(" wrong recipient ...... %0d", m_recip);
$display(" index out of range ... %0d", m_index);
$display(" value out of range ... %0d", m_value);
$display(" length wrong ......... %0d", m_length);
$display(" not a defined request %0d", m_unknown);
$display(" not a standard request . %0d", m_notstd);
$display("");
$display(" directed checks ........ %0d", chk_dir);
$display(" random checks .......... %0d", chk_rnd);
$display(" TOTAL checks ........... %0d", chk_dir + chk_rnd);
$display(" ERRORS ................. %0d", err);
if (err == 0) $display(" PASS"); else $display(" FAIL");
$finish;
end
endmoduleThe same bench in the other two languages. The reference table in each is written as a lookup rather than as the design's cascade of conditions — which is 30.2's independence item applied to a transcription: a checker that walks the specification the same way the design does will misread it the same way.
// =====================================================================
// tb_usb_req_legal_sv -- SystemVerilog testbench for usb_req_legal_sv.
//
// Phases 1-2 present the SAME directed stimulus as the Verilog bench,
// so their directed check counts must agree to the digit.
//
// The reference model here is written as a TABLE, not as the same
// cascade of conditions the design uses. That is deliberate and it is
// the review question from 30.2 applied to this block: a checker built
// the same way as the design reproduces the design's misreading of the
// specification along with it, and then agrees with it.
//
// PHASES
// 1 EXHAUSTIVE 3 states x 3 recipients x 16 request codes x 2
// directions = 288 verdicts, every one compared
// 2 BOUNDARY the value, index and length limits, one each
// 3 RANDOM supplementary, over the whole field space
// =====================================================================
`timescale 1ns/1ps
module tb_usb_req_legal_sv;
logic [1:0] dev_state;
logic dir_in;
logic [1:0] recipient, req_type;
logic [7:0] b_request;
logic [15:0] w_value, w_index, w_length;
logic [7:0] n_configs, n_interfaces;
logic [3:0] n_endpoints;
wire must_handle, must_stall;
wire [3:0] reason;
usb_req_legal_sv dut (
.dev_state(dev_state), .dir_in(dir_in), .recipient(recipient),
.req_type(req_type), .b_request(b_request),
.w_value(w_value), .w_index(w_index), .w_length(w_length),
.n_configs(n_configs), .n_interfaces(n_interfaces),
.n_endpoints(n_endpoints),
.must_handle(must_handle), .must_stall(must_stall), .reason(reason)
);
int chk_dir, chk_rnd, err;
bit in_random;
int m_handle, m_stall, m_notstd, m_state, m_dir, m_recip,
m_index, m_value, m_length, m_unknown;
int st, rc, rq, di, i;
task bump; begin
if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
end endtask
// -----------------------------------------------------------------
// The reference model: a table lookup, structurally unlike the DUT.
// ref_reason returns the code the specification requires.
// -----------------------------------------------------------------
function [3:0] ref_reason;
input [1:0] st_i;
input dir_i;
input [1:0] rcp_i;
input [1:0] typ_i;
input [7:0] req_i;
input [15:0] val_i, idx_i, len_i;
input [7:0] ncfg, nif;
input [3:0] nep;
// per-request attributes, looked up rather than recomputed
logic known, wants_in, zero_len;
logic [2:0] recip_mask; // bit0 DEVICE, bit1 INTERFACE, bit2 ENDPOINT
logic [2:0] state_mask; // bit0 DEFAULT, bit1 ADDRESS, bit2 CONFIGURED
logic idx_ok, val_ok;
begin
known = 1'b1; wants_in = 1'b0; zero_len = 1'b0;
recip_mask = 3'b001; state_mask = 3'b110;
case (req_i)
8'd0: begin wants_in=1; recip_mask=3'b111; state_mask=3'b110; end // GET_STATUS
8'd1: begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // CLEAR_FEATURE
8'd3: begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // SET_FEATURE
8'd5: begin zero_len=1; recip_mask=3'b001; state_mask=3'b111; end // SET_ADDRESS
8'd6: begin wants_in=1; recip_mask=3'b001; state_mask=3'b111; end // GET_DESCRIPTOR
8'd7: begin recip_mask=3'b001; state_mask=3'b111; end // SET_DESCRIPTOR
8'd8: begin wants_in=1; recip_mask=3'b001; state_mask=3'b110; end // GET_CONFIGURATION
8'd9: begin zero_len=1; recip_mask=3'b001; state_mask=3'b110; end // SET_CONFIGURATION
8'd10: begin wants_in=1; recip_mask=3'b010; state_mask=3'b100; end // GET_INTERFACE
8'd11: begin zero_len=1; recip_mask=3'b010; state_mask=3'b100; end // SET_INTERFACE
8'd12: begin wants_in=1; recip_mask=3'b100; state_mask=3'b100; end // SYNCH_FRAME
default: known = 1'b0;
endcase
if (rcp_i == 2'd1)
idx_ok = (st_i == 2'd2) ? (idx_i[7:0] < nif) : (idx_i[7:0] == 8'd0);
else if (rcp_i == 2'd2)
idx_ok = (st_i == 2'd2) ? (idx_i[3:0] <= nep) : (idx_i[3:0] == 4'd0);
else
idx_ok = 1'b1;
if (req_i == 8'd5) val_ok = (val_i <= 16'd127);
else if (req_i == 8'd9) val_ok = (val_i[7:0] <= ncfg);
else val_ok = 1'b1;
if (typ_i != 2'd0) ref_reason = 4'd1;
else if (!known) ref_reason = 4'd2;
else if (dir_i !== wants_in) ref_reason = 4'd4;
else if (!recip_mask[rcp_i] || rcp_i == 2'd3) ref_reason = 4'd5;
else if (!state_mask[st_i]) ref_reason = 4'd3;
else if (!idx_ok) ref_reason = 4'd6;
else if (!val_ok) ref_reason = 4'd7;
else if (zero_len && (len_i != 16'd0)) ref_reason = 4'd8;
else ref_reason = 4'd0;
end
endfunction
task check_now(string what);
logic [3:0] exp_r;
logic exp_h, exp_s;
begin
#1;
exp_r = ref_reason(dev_state, dir_in, recipient, req_type, b_request,
w_value, w_index, w_length,
n_configs, n_interfaces, n_endpoints);
exp_h = (exp_r == 4'd0);
exp_s = (exp_r != 4'd0) && (exp_r != 4'd1);
bump;
if (reason !== exp_r) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %s reason: got %0d expected %0d (state=%0d rcp=%0d req=%0d dir=%0d)",
what, reason, exp_r, dev_state, recipient, b_request, dir_in);
end
bump;
if (must_handle !== exp_h) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %s must_handle: got %0d expected %0d", what, must_handle, exp_h);
end
bump;
if (must_stall !== exp_s) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %s must_stall: got %0d expected %0d", what, must_stall, exp_s);
end
// tallies -- what this sweep actually reached
case (exp_r)
4'd0: m_handle = m_handle + 1;
4'd1: m_notstd = m_notstd + 1;
4'd2: m_unknown = m_unknown + 1;
4'd3: m_state = m_state + 1;
4'd4: m_dir = m_dir + 1;
4'd5: m_recip = m_recip + 1;
4'd6: m_index = m_index + 1;
4'd7: m_value = m_value + 1;
4'd8: m_length = m_length + 1;
endcase
if (exp_s) m_stall = m_stall + 1;
end
endtask
// -----------------------------------------------------------------
// PHASE 1 -- the exhaustive sweep.
//
// DENOMINATOR, derived:
// device states DEFAULT, ADDRESS, CONFIGURED 3
// recipients DEVICE, INTERFACE, ENDPOINT 3
// request codes 0..15: the eleven standard requests plus
// five undefined ones, which is the case the
// specification is most often wrong about 16
// directions device-to-host, host-to-device 2
// ------------------------------------------------------------------
// 3 x 3 x 16 x 2 = 288
//
// Recipient 3 is excluded: it is reserved, and a sweep that included
// it would be measuring the reserved encoding rather than the table.
// wValue, wIndex and wLength are held at LEGAL values here so that
// the state/recipient/direction rules are what is being measured;
// their bounds are phase 2, one scenario each.
// -----------------------------------------------------------------
task phase_sweep;
begin
req_type = 2'd0;
n_configs = 8'd2;
n_interfaces = 8'd3;
n_endpoints = 4'd4;
w_value = 16'd0;
w_index = 16'd0;
w_length = 16'd0;
for (st = 0; st < 3; st = st + 1)
for (rc = 0; rc < 3; rc = rc + 1)
for (rq = 0; rq < 16; rq = rq + 1)
for (di = 0; di < 2; di = di + 1) begin
dev_state = 2'(st);
recipient = 2'(rc);
b_request = 8'(rq);
dir_in = 1'(di);
// GET_DESCRIPTOR and GET_STATUS legitimately carry a length
w_length = (rq == 0 || rq == 6 || rq == 8 || rq == 10 || rq == 12)
? 16'd8 : 16'd0;
check_now("sweep");
end
end
endtask
// -----------------------------------------------------------------
// PHASE 2 -- the bounds, one scenario each.
// -----------------------------------------------------------------
task phase_bounds;
begin
req_type = 2'd0; n_configs = 8'd2; n_interfaces = 8'd3; n_endpoints = 4'd4;
// SET_ADDRESS: the address field is seven bits wide
dev_state=2'd1; recipient=2'd0; b_request=8'd5; dir_in=1'b0;
w_index=0; w_length=0;
w_value = 16'd127; check_now("addr 127 legal");
w_value = 16'd128; check_now("addr 128 illegal");
// SET_CONFIGURATION: zero is legal and means "unconfigure"
dev_state=2'd2; recipient=2'd0; b_request=8'd9; dir_in=1'b0;
w_value = 16'd0; check_now("config 0 legal");
w_value = 16'd2; check_now("config 2 legal");
w_value = 16'd3; check_now("config 3 illegal");
// a no-data-stage request that asks for data
w_value = 16'd1; w_length = 16'd8; check_now("set-config with length");
w_length = 16'd0;
// interface index bounds, and the Address-state special case
dev_state=2'd2; recipient=2'd1; b_request=8'd10; dir_in=1'b1;
w_length=16'd1;
w_index = 16'd2; check_now("iface 2 exists");
w_index = 16'd3; check_now("iface 3 does not");
dev_state=2'd1; recipient=2'd1; b_request=8'd0; dir_in=1'b1;
w_length=16'd2;
w_index = 16'd0; check_now("iface 0 in address state");
w_index = 16'd1; check_now("iface 1 in address state");
// endpoint index bounds
dev_state=2'd2; recipient=2'd2; b_request=8'd0; dir_in=1'b1;
w_length=16'd2;
w_index = 16'd4; check_now("ep 4 exists");
w_index = 16'd5; check_now("ep 5 does not");
// a class request is not this block's to reject
dev_state=2'd2; recipient=2'd1; req_type=2'd1; b_request=8'd10;
dir_in=1'b1; w_index=16'd0; w_length=16'd1;
check_now("class request passes through");
req_type = 2'd0;
end
endtask
// -----------------------------------------------------------------
// PHASE 3 -- random over the whole field space.
// -----------------------------------------------------------------
task phase_random;
begin
in_random = 1;
for (i = 0; i < 20000; i = i + 1) begin
dev_state = ($urandom_range(2));
recipient = ($urandom_range(2));
req_type = (($urandom_range(99)) < 85) ? 2'd0 : ($urandom_range(2));
b_request = (($urandom_range(99)) < 80) ? ($urandom_range(12))
: ($urandom_range(255));
dir_in = ($urandom_range(1));
w_value = (($urandom_range(99)) < 60) ? ($urandom_range(131))
: ($urandom_range(65535));
w_index = (($urandom_range(99)) < 70) ? ($urandom_range(7))
: ($urandom_range(65535));
w_length = (($urandom_range(99)) < 50) ? 16'd0 : ($urandom_range(63));
n_configs = ($urandom_range(3)) + 8'd1;
n_interfaces = ($urandom_range(3)) + 8'd1;
n_endpoints = ($urandom_range(7));
check_now("random");
end
in_random = 0;
end
endtask
initial begin
chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
m_handle=0; m_stall=0; m_notstd=0; m_state=0; m_dir=0; m_recip=0;
m_index=0; m_value=0; m_length=0; m_unknown=0;
phase_sweep;
$display(" phase 1 exhaustive : %0d checks, %0d errors (288 verdicts)",
chk_dir, err);
phase_bounds;
$display(" phase 2 bounds : %0d checks, %0d errors", chk_dir, err);
$display(" ---- DIRECTED-ONLY : %0d checks, %0d errors ----", chk_dir, err);
phase_random;
$display("");
$display(" verdicts reached (all phases)");
$display(" must handle ............ %0d", m_handle);
$display(" must stall ............. %0d", m_stall);
$display(" undefined in state ... %0d", m_state);
$display(" wrong direction ...... %0d", m_dir);
$display(" wrong recipient ...... %0d", m_recip);
$display(" index out of range ... %0d", m_index);
$display(" value out of range ... %0d", m_value);
$display(" length wrong ......... %0d", m_length);
$display(" not a defined request %0d", m_unknown);
$display(" not a standard request . %0d", m_notstd);
$display("");
$display(" directed checks ........ %0d", chk_dir);
$display(" random checks .......... %0d", chk_rnd);
$display(" TOTAL checks ........... %0d", chk_dir + chk_rnd);
$display(" ERRORS ................. %0d", err);
if (err == 0) $display(" PASS"); else $display(" FAIL");
$finish;
end
endmodule-- =====================================================================
-- tb_usb_req_legal -- VHDL-2008 testbench for usb_req_legal.
--
-- Phases 1-2 present the SAME directed stimulus as the Verilog and
-- SystemVerilog benches, so their directed check counts must agree.
--
-- The reference model is a table lookup written independently of the
-- design's structure, for the reason in 30.2: a checker built the same
-- way as the design reproduces the design's misreading of the
-- specification and then agrees with it.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity tb_usb_req_legal is
end entity tb_usb_req_legal;
architecture sim of tb_usb_req_legal is
signal dev_state, recipient, req_type : unsigned(1 downto 0) := (others => '0');
signal dir_in : std_logic := '0';
signal b_request : unsigned(7 downto 0) := (others => '0');
signal w_value : unsigned(15 downto 0) := (others => '0');
signal w_index : unsigned(15 downto 0) := (others => '0');
signal w_length : unsigned(15 downto 0) := (others => '0');
signal n_configs, n_interfaces : unsigned(7 downto 0) := (others => '0');
signal n_endpoints : unsigned(3 downto 0) := (others => '0');
signal must_handle, must_stall : std_logic;
signal reason : unsigned(3 downto 0);
function b2i (s : std_logic) return integer is
begin
if s = '1' then return 1; else return 0; end if;
end function b2i;
begin
dut : entity work.usb_req_legal
port map (dev_state => dev_state, dir_in => dir_in,
recipient => recipient, req_type => req_type,
b_request => b_request, w_value => w_value,
w_index => w_index, w_length => w_length,
n_configs => n_configs, n_interfaces => n_interfaces,
n_endpoints => n_endpoints,
must_handle => must_handle, must_stall => must_stall,
reason => reason);
stim : process
variable chk_dir, chk_rnd, errs, shown : natural := 0;
variable in_random : boolean := false;
variable m_handle, m_stall, m_notstd, m_state, m_dirn, m_recip : natural := 0;
variable m_index, m_value, m_length, m_unknown : natural := 0;
variable seed1 : positive := 733_921;
variable seed2 : positive := 55_411;
procedure bump is
begin
if in_random then chk_rnd := chk_rnd + 1; else chk_dir := chk_dir + 1; end if;
end procedure bump;
-- The reference table, independent of the design's structure.
impure function ref_reason return natural is
variable known, wants_in, zero_len : boolean;
variable rmask, smask : std_logic_vector(2 downto 0);
variable idx_ok, val_ok : boolean;
begin
known := true; wants_in := false; zero_len := false;
rmask := "001"; smask := "110";
case to_integer(b_request) is
when 0 => wants_in := true; rmask := "111"; smask := "110";
when 1 => zero_len := true; rmask := "111"; smask := "110";
when 3 => zero_len := true; rmask := "111"; smask := "110";
when 5 => zero_len := true; rmask := "001"; smask := "111";
when 6 => wants_in := true; rmask := "001"; smask := "111";
when 7 => rmask := "001"; smask := "111";
when 8 => wants_in := true; rmask := "001"; smask := "110";
when 9 => zero_len := true; rmask := "001"; smask := "110";
when 10 => wants_in := true; rmask := "010"; smask := "100";
when 11 => zero_len := true; rmask := "010"; smask := "100";
when 12 => wants_in := true; rmask := "100"; smask := "100";
when others => known := false;
end case;
if recipient = 1 then
if dev_state = 2 then idx_ok := w_index(7 downto 0) < n_interfaces;
else idx_ok := w_index(7 downto 0) = 0;
end if;
elsif recipient = 2 then
if dev_state = 2 then idx_ok := w_index(3 downto 0) <= n_endpoints;
else idx_ok := w_index(3 downto 0) = 0;
end if;
else idx_ok := true;
end if;
if to_integer(b_request) = 5 then val_ok := w_value <= 127;
elsif to_integer(b_request) = 9 then val_ok := w_value(7 downto 0) <= n_configs;
else val_ok := true;
end if;
if req_type /= 0 then return 1; end if;
if not known then return 2; end if;
if (dir_in = '1') /= wants_in then return 4; end if;
if recipient > 2 or rmask(to_integer(recipient)) = '0' then return 5; end if;
if smask(to_integer(dev_state)) = '0' then return 3; end if;
if not idx_ok then return 6; end if;
if not val_ok then return 7; end if;
if zero_len and w_length /= 0 then return 8; end if;
return 0;
end function ref_reason;
procedure check_now (what : string) is
variable exp_r : natural;
variable exp_h, exp_s : integer;
begin
wait for 1 ns;
exp_r := ref_reason;
if exp_r = 0 then exp_h := 1; else exp_h := 0; end if;
if exp_r /= 0 and exp_r /= 1 then exp_s := 1; else exp_s := 0; end if;
bump;
if to_integer(reason) /= exp_r then
errs := errs + 1;
if (not in_random) and shown < 40 then
shown := shown + 1;
report " ** " & what & " reason: got " &
integer'image(to_integer(reason)) & " expected " &
integer'image(exp_r) severity warning;
end if;
end if;
bump;
if b2i(must_handle) /= exp_h then
errs := errs + 1;
if (not in_random) and shown < 40 then
shown := shown + 1;
report " ** " & what & " must_handle" severity warning;
end if;
end if;
bump;
if b2i(must_stall) /= exp_s then
errs := errs + 1;
if (not in_random) and shown < 40 then
shown := shown + 1;
report " ** " & what & " must_stall" severity warning;
end if;
end if;
case exp_r is
when 0 => m_handle := m_handle + 1;
when 1 => m_notstd := m_notstd + 1;
when 2 => m_unknown := m_unknown + 1;
when 3 => m_state := m_state + 1;
when 4 => m_dirn := m_dirn + 1;
when 5 => m_recip := m_recip + 1;
when 6 => m_index := m_index + 1;
when 7 => m_value := m_value + 1;
when others => m_length := m_length + 1;
end case;
if exp_s = 1 then m_stall := m_stall + 1; end if;
end procedure check_now;
impure function rnd (n : positive) return natural is
variable x : real;
begin
uniform(seed1, seed2, x);
return natural(real(n - 1) * x);
end function rnd;
begin
-- ---- PHASE 1 : 3 x 3 x 16 x 2 = 288 verdicts ----
req_type <= "00"; n_configs <= to_unsigned(2, 8);
n_interfaces <= to_unsigned(3, 8); n_endpoints <= to_unsigned(4, 4);
w_value <= (others => '0'); w_index <= (others => '0');
for st in 0 to 2 loop
for rc in 0 to 2 loop
for rq in 0 to 15 loop
for di in 0 to 1 loop
dev_state <= to_unsigned(st, 2);
recipient <= to_unsigned(rc, 2);
b_request <= to_unsigned(rq, 8);
if di = 1 then dir_in <= '1'; else dir_in <= '0'; end if;
if rq = 0 or rq = 6 or rq = 8 or rq = 10 or rq = 12 then
w_length <= to_unsigned(8, 16);
else
w_length <= to_unsigned(0, 16);
end if;
check_now("sweep");
end loop;
end loop;
end loop;
end loop;
report " phase 1 exhaustive : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors (288 verdicts)";
-- ---- PHASE 2 : the bounds ----
dev_state <= "01"; recipient <= "00"; b_request <= to_unsigned(5, 8);
dir_in <= '0'; w_index <= (others => '0'); w_length <= (others => '0');
w_value <= to_unsigned(127, 16); check_now("addr 127 legal");
w_value <= to_unsigned(128, 16); check_now("addr 128 illegal");
dev_state <= "10"; b_request <= to_unsigned(9, 8);
w_value <= to_unsigned(0, 16); check_now("config 0 legal");
w_value <= to_unsigned(2, 16); check_now("config 2 legal");
w_value <= to_unsigned(3, 16); check_now("config 3 illegal");
w_value <= to_unsigned(1, 16); w_length <= to_unsigned(8, 16);
check_now("set-config with length");
w_length <= to_unsigned(0, 16);
dev_state <= "10"; recipient <= "01"; b_request <= to_unsigned(10, 8);
dir_in <= '1'; w_length <= to_unsigned(1, 16);
w_index <= to_unsigned(2, 16); check_now("iface 2 exists");
w_index <= to_unsigned(3, 16); check_now("iface 3 does not");
dev_state <= "01"; b_request <= to_unsigned(0, 8);
w_length <= to_unsigned(2, 16);
w_index <= to_unsigned(0, 16); check_now("iface 0 in address state");
w_index <= to_unsigned(1, 16); check_now("iface 1 in address state");
dev_state <= "10"; recipient <= "10"; b_request <= to_unsigned(0, 8);
dir_in <= '1'; w_length <= to_unsigned(2, 16);
w_index <= to_unsigned(4, 16); check_now("ep 4 exists");
w_index <= to_unsigned(5, 16); check_now("ep 5 does not");
recipient <= "01"; req_type <= "01"; b_request <= to_unsigned(10, 8);
dir_in <= '1'; w_index <= (others => '0'); w_length <= to_unsigned(1, 16);
check_now("class request passes through");
req_type <= "00";
report " phase 2 bounds : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors";
report " ---- DIRECTED-ONLY : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors ----";
-- ---- PHASE 3 : random ----
in_random := true;
for i in 0 to 19999 loop
dev_state <= to_unsigned(rnd(3), 2);
recipient <= to_unsigned(rnd(3), 2);
if rnd(100) < 85 then req_type <= "00";
else req_type <= to_unsigned(rnd(3), 2); end if;
if rnd(100) < 80 then b_request <= to_unsigned(rnd(13), 8);
else b_request <= to_unsigned(rnd(256), 8); end if;
if rnd(2) = 1 then dir_in <= '1'; else dir_in <= '0'; end if;
if rnd(100) < 60 then w_value <= to_unsigned(rnd(132), 16);
else w_value <= to_unsigned(rnd(65536), 16); end if;
if rnd(100) < 70 then w_index <= to_unsigned(rnd(8), 16);
else w_index <= to_unsigned(rnd(65536), 16); end if;
if rnd(100) < 50 then w_length <= (others => '0');
else w_length <= to_unsigned(rnd(64), 16); end if;
n_configs <= to_unsigned(rnd(4) + 1, 8);
n_interfaces <= to_unsigned(rnd(4) + 1, 8);
n_endpoints <= to_unsigned(rnd(8), 4);
check_now("random");
end loop;
in_random := false;
report " verdicts reached (all phases)";
report " must handle ............ " & integer'image(m_handle);
report " must stall ............. " & integer'image(m_stall);
report " undefined in state ... " & integer'image(m_state);
report " wrong direction ...... " & integer'image(m_dirn);
report " wrong recipient ...... " & integer'image(m_recip);
report " index out of range ... " & integer'image(m_index);
report " value out of range ... " & integer'image(m_value);
report " length wrong ......... " & integer'image(m_length);
report " not a defined request " & integer'image(m_unknown);
report " not a standard request . " & integer'image(m_notstd);
report " directed checks ........ " & integer'image(chk_dir);
report " random checks .......... " & integer'image(chk_rnd);
report " TOTAL checks ........... " & integer'image(chk_dir + chk_rnd);
report " ERRORS ................. " & integer'image(errs);
if errs = 0 then report " PASS"; else report " FAIL" severity failure; end if;
wait;
end process stim;
end architecture sim;12. What A Compliance Test Actually Checks
Worth knowing so that a review can be honest about what it is preparing for. Categories, and which of them an RTL review can touch:
CATEGORY RTL REVIEW CAN SETTLED BY
-------------------------- -------------- ---------------------------
descriptor correctness yes -- the a descriptor parser, and
content is reading the class document
data we author
Chapter 9 behaviour yes -- this a legality table, and the
chapter sweep above
device state transitions yes a state-machine review and
an exhaustive sweep
endpoint legality yes descriptors vs what the
controller implements
transfer behaviour partly simulation for the logic;
a host for the scheduling
class-specific behaviour partly the class document, which
is a different document
electrical and timing NO instruments, on a board
interoperability NO many hosts, over time13. The Checklist
1 SCOPE
[ ] which specification, which version, which class documents
[ ] which speeds are claimed
[ ] every claim in the datasheet maps to a requirement somewhere
2 DESCRIPTORS
[ ] every descriptor parses, and parses to what was intended
[ ] lengths and totals are consistent with the contents
[ ] every endpoint described exists in the controller, and every
endpoint in the controller is described
[ ] maximum packet sizes are legal for the speed claimed
[ ] string indices that are referenced exist
3 CHAPTER 9 BEHAVIOUR
[ ] a state x request table exists, in writing
[ ] every "undefined" cell is a DECISION to reject, not a default
[ ] field bounds enforce the SPECIFIED range, not the field width
[ ] bounds are checked for being too strict as well as too loose
[ ] the data direction agrees with the request
[ ] a class or vendor request is not rejected by the standard layer
4 DEVICE STATES
[ ] every transition in the specification exists in the design
[ ] a bus reset returns the device to Default from every state
[ ] SET_ADDRESS to zero and SET_CONFIGURATION to zero both work
[ ] behaviour after configuration is tested, not only enumeration
5 EVIDENCE
[ ] the legality table is swept exhaustively, with a derived
denominator and named exclusions
[ ] every verdict class is reached at least once
[ ] the reference table is written independently of the design
[ ] a reason code accompanies every rejection
6 HONESTY
[ ] the review says which categories it CANNOT settle
[ ] electrical and interoperability are named as out of scope
[ ] lab time is booked
[ ] nobody has written "compliant" where "conformant to our
reading" is meant14. Exercises
1 THE TABLE
Extend the state x request table to include the Suspended state.
Which rows change? Which requests may a suspended device be
required to answer, and what has to be true for it to do so?
2 UNDEFINED
Pick three "undefined" cells and write, for each, what a device
that answered would have to return, and why no such value exists.
3 TOO STRICT
Find a second bounds check in the specimen that could be made
wrong in the too-strict direction, and write the host behaviour it
would break.
4 VERILOG
Add descriptor-type legality to GET_DESCRIPTOR: which descriptor
types may be requested in which state, and with what index.
5 SYSTEMVERILOG
Implement it with the enum extended. Does the compiler now catch
anything it did not?
6 VHDL
Implement it, and say what the compiler catches that the other two
do not.
7 DENOMINATOR
Derive the denominator for the extended table. Is it still
exhaustible? At what point does it stop being, and what replaces
exhaustion?
8 EVIDENCE CLASSIFICATION
For each of these, say which of the four things called compliance
in section 1 it settles: a passing simulation; a device that
enumerates on three laptops; a descriptor parser reporting clean;
an eye diagram; a USB-IF test event result.
9 THE HONEST PARAGRAPH
Write the paragraph a review report should contain about what the
review did not establish. Four items minimum.15. The Interview Answer
"Our device works on every machine we have tried. What is left to worry about?"
That sentence describes interoperability with the hosts you own, which is one of four different things people call compliance, and it is the weakest of them as evidence.
Working on every machine in the building means every host stack you tried sends a subset of the defined requests, in an order you happen to handle, and your device answers them. Different stacks send different subsets. The requests your device gets wrong are, by construction, the ones nobody on your desk sends — which is exactly why functional testing cannot close this gap. Enumeration in particular is one path through the state machine, and conformance is a property of the whole machine: the requirements that apply after configuration are not exercised by plugging it in.
So the thing I would want is a state-by-request table, in writing, checked
exhaustively. It is small — three device states, eleven standard requests, three
recipients, both directions is 288 verdicts — and it is one of the rare things in
hardware you can check completely. The cells that matter most are the ones the
specification calls undefined, because "undefined" gets read as "don't care" and
it is not: a device that answers GET_CONFIGURATION before it has an address
returns a value that cannot be true.
I would also check the bounds in both directions. Too loose is the obvious one —
accepting an address above 127 because the field is sixteen bits wide. Too strict
is the one review misses, because rejecting things feels safe: SET_CONFIGURATION
with value zero is legal and means unconfigure, and a device that treats it as an
error breaks a host that is deliberately tearing down.
And then the honest part. None of that touches electrical compliance — eye diagrams, inrush, droop — and none of it touches interoperability with a stack that does something the specification permits it not to do. Those are settled by instruments and by time, not by simulation. The practical consequence is a schedule one: book lab time early and treat the first electrical run as a milestone, because the RTL can be finished and the product can still be six weeks from a logo.
The word I would be careful with is "compliant". What a review like this produces is conformance to our reading of the document, which is worth a great deal and is not the same thing as a certification result.
16. What Carries Forward
THE DISTINCTION
o four different things are called compliance: team review, functional
verification, specification conformance, official certification --
and each proves only what it proves
o "works on every machine we tried" is interoperability with the hosts
you own, and it is the weakest evidence of the four
o enumeration is one PATH; conformance is a property of the whole
state machine
THE ITEMS
o a state x request table belongs in writing, and every UNDEFINED cell
is a decision to reject rather than a don't-care
o field bounds enforce the SPECIFIED range, not the field width -- and
a bound that is too strict is a conformance defect too
o a class request is not the standard layer's to reject
o a reason code beside every rejection converts a lab day into ten
minutes
THE EVIDENCE
o a legality table is exhaustible: 288 verdicts, derived denominator,
named exclusions, every verdict class reached
o a conformance defect is intrinsically LOW-YIELD to test and
HIGH-CONSEQUENCE to ship, so mutation scores must not be used to
rank findings -- only to confirm each cell is reached
o the language gradient is real for transcribed tables: a silent
default, a warning, a compile error -- and none of the three helps
with a row that is present and wrong
THE HONESTY
o electrical and interoperability cannot be settled here, at all
o the review report says what it did not establish
o "conformant to our reading" is the true claim; "compliant" is notThe next chapter moves outward again: the controller is correct and conformant, and now it has to live inside a chip with other people's clocks, other people's resets, and firmware that was written against a datasheet.
Continue learning
Related tutorials
- 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.
- 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
Descriptor Issues
A descriptor set is described three times by three different fields, and the host walks it by one while reading it by another — so when they disagree the error lands on a field that is perfectly correct.
- Related topic
DMA Integration
A descriptor has a byte count and the wire has packets, and the rule that converts one to the other is not ceil(length / packet size) — the version that is hangs on exactly the buffer sizes everybody uses.
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.
