USB · Module 20
USB 3.x vs USB 2.0 Differences
A SuperSpeed-capable device behind a USB 2 hub is a USB 2 device: capability is a property of the link that trained, never of the descriptor the device published.
Four chapters ago this module started with SuperSpeed concepts. Since then we have built a credit engine, a dual-bus arbiter, an LTSSM and a packet decoder. This chapter puts the two buses side by side — and then answers the one question all of that machinery exists to serve.
It is not "what is different about USB 3?" It is: given a device sitting in front of me right now, which set of rules applies?
That question has a wrong answer that almost everyone reaches for first.
1. The Two Buses, Layer by Layer
USB 3 did not replace USB 2. Chapter 20.2 established the physical reality: two independent buses in one cable, and a device may end up operating on either.
They are not variations on one stack. SuperSpeed has a layer that USB 2 does not have at all.
The two stacks in one cable
That missing box is the reason this module needed Chapter 20.4 at all. A header CRC failure is a link-layer event, and USB 2 has no link layer — so it has no LBAD, no packet-level retry, and no per-link power states. USB 2's only equivalent of "this transfer went wrong" is the protocol-layer one.
2. Every Mechanism That Differs
| USB 2 | SuperSpeed | |
|---|---|---|
| Flow control | host polls; device answers when asked | device is given credits and sends when it has them |
| Device busy | NAK — "ask me again" (host keeps polling) | NRDY then ERDY — "I will tell you" |
| Direction | half duplex, one pair, one direction at a time | full duplex, separate TX and RX pairs |
| Bursting | one packet per token | up to bMaxBurst packets back to back |
| Streams | none | multiple logical streams on one bulk endpoint |
| Link training | none needed | mandatory LTSSM (ch 20.3) |
| Idle | bus-wide SUSPEND | per-link U1 / U2, plus U3 for suspend |
| Unit load | 100 mA | 150 mA |
| Bus-powered max | 5 unit loads = 500 mA | 6 unit loads = 900 mA |
| Self-powered max | 1 unit load | 1 unit load |
| Header protection | no separate header CRC | 14-byte header with its own CRC-16 |
| Enumeration | reset, address, descriptors | the same, plus a BOS descriptor |
Read down the middle column and it becomes obvious that these are not tweaks. The polling-versus-credits row alone changes which side initiates traffic, which is why Chapter 20.1 needed a whole credit engine rather than a modified poller.
3. The Question, and the Wrong Input
Now the question a driver, a host controller, and a power manager all have to answer:
"Is this a SuperSpeed device?"The instinct is to answer it by asking the device. The device knows: it has a BOS descriptor, it publishes a SuperSpeed device capability, it will happily tell you it supports 5 Gb/s.
That is the wrong input, and it is wrong in a way that produces working-but-broken systems.
"Is this a SuperSpeed device?" is not a question about the device at all. It is a question about the link that was actually established. Consider three situations in which a genuinely SuperSpeed-capable device is not a SuperSpeed device:
- It is plugged into a USB 2 hub. The hub has no SuperSpeed pair to offer. The device enumerated on D+/D- at high speed.
- It is plugged into a USB 3 port through a cable missing the SuperSpeed wires — a charge-only cable, or a damaged one. The port offered a SuperSpeed pair; nothing came back.
- It is plugged into a USB 3 port with a good cable, and training failed. The LTSSM from Chapter 20.3 went
RX.Detect → Polling → timeout → Compliance. Everything was capable. Nothing trained.
In all three, the device's descriptors say SuperSpeed and the device is a high-speed device. The polling rules apply. NAK, not ERDY. 100 mA unit loads, not 150. No streams. No bursting. No U1.
So the resolver in this chapter produces two separate outputs that must never be conflated:
Two chains, two answers, and only one of them decides anything
4. What We Are Building
usb3_capability_resolve
what the device CLAIMS what the LINK did
---------------------- -----------------
dev_ss_capable port_ss_capable
bos_present ss_link_up
unit_loads_req [2:0] usb2_speed [1:0]
self_powered
outputs
-------
ss_capable_reported <- the claim. Gates nothing.
ss_operating <- the truth. Gates everything below.
op_mode [2:0] NONE / LOW / FULL / HIGH / SUPER
credit_flow 0 = host polls, 1 = credits
async_notify 0 = NAK, 1 = NRDY / ERDY
burst_allowed
streams_allowed
link_pm_u1u2 0 = bus suspend, 1 = U1 / U2
unit_load_ma [7:0] 100 or 150
unit_loads_grant [2:0] after BOTH ceilings are applied
max_power_ma [9:0] grant x unit load, up to 900
power_capped
n_superspeed [31:0]
n_fallback [31:0] <- the diagnostic that earns its area
n_capped [31:0]
n_unconfigured [31:0]n_fallback counts devices that could have run at SuperSpeed and did not. It is the single most valuable counter in the block, because that failure is otherwise invisible: the device enumerates, the device works, the user notices nothing except that a transfer takes twenty times as long as the box promised. Without this counter there is no signal at all that a cable, a hub or a training sequence is at fault.
The input space is small enough to enumerate completely:
2 (dev_ss_capable) x 2 (bos_present) x 2 (port_ss_capable)
x 2 (ss_link_up) x 4 (usb2_speed) x 8 (unit_loads_req)
x 2 (self_powered)
= 1024 points, ALL of them reachable5. Verilog-2005 Implementation
// usb3_capability_resolve -- which mechanism applies at which speed, and the
// one question software keeps answering with the wrong input.
//
// USB 3 did not replace USB 2. It added a second, parallel bus in the same
// cable (Chapter 20.2), and a device can end up operating on either one. Every
// mechanism the two buses define differs:
//
// USB 2 SuperSpeed
// flow host POLLS the device device is GIVEN CREDITS
// busy device answers NAK device sends NRDY, later ERDY
// unit load 100 mA 150 mA
// bursting no yes, up to bMaxBurst packets
// streams no yes, for bulk endpoints
// idle bus-wide SUSPEND per-link U1 / U2
//
// THE QUESTION, AND THE WRONG INPUT
//
// "Is this a SuperSpeed device?" is not a question about the device. It is a
// question about THE LINK THAT WAS ACTUALLY ESTABLISHED.
//
// A SuperSpeed-capable device plugged into a USB 2 hub, or into a USB 3 port
// with a broken SuperSpeed pair, or into a cable missing the SuperSpeed
// wires, IS A USB 2 DEVICE. It reports SuperSpeed capability in its BOS
// descriptor, it is physically capable of SuperSpeed, and none of that is
// relevant: the link trained at high speed, so high-speed rules apply.
//
// So this module produces TWO separate outputs that are easy to conflate and
// must never be:
//
// ss_capable_reported what the descriptors CLAIM. Read from the device.
// ss_operating what is ACTUALLY running. Requires the device to be
// capable, the port to be capable, AND the SuperSpeed
// link to have trained (Chapter 20.3).
//
// Every mechanism selection below is gated on ss_operating. A design that
// gates any of them on ss_capable_reported enables SuperSpeed behaviour on a
// USB 2 link -- which is the failure Chapter 20.2 warned about, arriving from
// the software side instead of the hardware side.
module usb3_capability_resolve (
input wire clk,
input wire rst_n,
// what the device claims
input wire dev_ss_capable, // the device's hardware supports SS
input wire bos_present, // and it published a BOS descriptor
input wire [2:0] unit_loads_req, // bMaxPower, in unit loads (0..7)
input wire self_powered, // it has its own supply
// what the link actually did
input wire port_ss_capable, // the port/hub offers a SS pair
input wire ss_link_up, // and the SS link TRAINED (ch 20.3)
input wire [1:0] usb2_speed, // the USB 2 link that did come up
output wire ss_capable_reported,
output wire ss_operating,
output wire [2:0] op_mode,
output wire credit_flow, // 0 = host polls, 1 = credits
output wire async_notify, // 0 = NAK, 1 = NRDY/ERDY
output wire burst_allowed,
output wire streams_allowed,
output wire link_pm_u1u2, // 0 = bus suspend only, 1 = U1/U2
output wire [7:0] unit_load_ma, // 100 or 150
output wire [2:0] unit_loads_grant,
output wire [9:0] max_power_ma,
output wire power_capped,
output reg [31:0] n_superspeed,
output reg [31:0] n_fallback, // SS-capable but NOT operating at SS
output reg [31:0] n_capped,
output reg [31:0] n_unconfigured
);
localparam [1:0] SP_NONE = 2'd0, SP_LOW = 2'd1, SP_FULL = 2'd2, SP_HIGH = 2'd3;
localparam [2:0] OP_NONE = 3'd0,
OP_LOW = 3'd1,
OP_FULL = 3'd2,
OP_HIGH = 3'd3,
OP_SUPER = 3'd4;
// ---- The two capability answers, and they are NOT the same question ----
// What software can read out of the descriptors. Useful for logging and for
// telling a user "this device would be faster in a USB 3 port". Useful for
// NOTHING ELSE.
assign ss_capable_reported = dev_ss_capable && bos_present;
// What is actually running. All three terms are required: a capable device,
// a capable port, and a link that trained. Drop any one of them and the
// module starts enabling SuperSpeed mechanisms on a USB 2 link.
assign ss_operating = dev_ss_capable && port_ss_capable && ss_link_up;
// ---- Operating mode: SuperSpeed if it trained, otherwise whatever USB 2
// ---- actually negotiated. NOT "high speed because it is a USB 3 device".
assign op_mode = ss_operating ? OP_SUPER
: (usb2_speed == SP_HIGH) ? OP_HIGH
: (usb2_speed == SP_FULL) ? OP_FULL
: (usb2_speed == SP_LOW) ? OP_LOW
: OP_NONE;
// ---- Every mechanism, gated on ss_operating and never on the claim ----
assign credit_flow = ss_operating;
assign async_notify = ss_operating;
assign burst_allowed = ss_operating;
assign streams_allowed = ss_operating;
assign link_pm_u1u2 = ss_operating;
// ---- Power: the unit load is 150 mA on SuperSpeed, 100 mA on USB 2 ----
assign unit_load_ma = ss_operating ? 8'd150 : 8'd100;
// Two independent ceilings apply, and the lower one wins.
//
// a SELF-POWERED device may draw at most ONE unit load from the bus --
// it has its own supply and the bus budget is for everyone else
//
// a BUS-POWERED device may draw 6 unit loads on SuperSpeed, 5 on USB 2
wire [2:0] mode_ceiling = ss_operating ? 3'd6 : 3'd5;
wire [2:0] ceiling = self_powered ? 3'd1 : mode_ceiling;
assign unit_loads_grant = (unit_loads_req > ceiling) ? ceiling
: unit_loads_req;
assign power_capped = (unit_loads_req > ceiling);
// 6 unit loads x 150 mA = 900 mA, which is why this is 10 bits wide.
assign max_power_ma = {7'd0, unit_loads_grant} * {2'd0, unit_load_ma};
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_superspeed <= 32'd0;
n_fallback <= 32'd0;
n_capped <= 32'd0;
n_unconfigured <= 32'd0;
end else begin
if (ss_operating) n_superspeed <= n_superspeed + 32'd1;
// THE diagnostic worth having in real silicon: a device that could have
// run at SuperSpeed and did not. Every one of these is a cable, a hub,
// or a training failure, and without this counter it is invisible --
// the device works, just at a twentieth of the speed.
if (ss_capable_reported && !ss_operating)
n_fallback <= n_fallback + 32'd1;
if (power_capped) n_capped <= n_capped + 32'd1;
if (op_mode == OP_NONE) n_unconfigured <= n_unconfigured + 32'd1;
end
end
endmoduleThree points worth dwelling on.
ss_operating needs all three terms, and the mutation matrix measures what each one is worth. It is tempting to think ss_link_up alone is sufficient — surely the link cannot be up unless both ends are capable? In a correct system, yes. But ss_link_up arrives from the LTSSM, and an LTSSM that has been forced into Compliance mode, or a register left set from a previous enumeration, can assert it without a real partner. Requiring all three makes the resolver robust against a lie from any one of its inputs.
Two ceilings, and the lower one wins. mode_ceiling is the bus's limit (6 unit loads on SuperSpeed, 5 on USB 2); ceiling then narrows that to 1 for a self-powered device. Writing it as two steps rather than one nested conditional is what makes the rule "a self-powered device draws one unit load whichever bus it is on" visible rather than emergent.
max_power_ma is a plain product, deliberately. The temptation is a lookup table of the six legal values, and that is how the off-by-one arrives: the table gets written for SuperSpeed and reused for USB 2. grant * unit_load cannot be wrong for one bus and right for the other.
6. SystemVerilog Implementation
The SystemVerilog build hoists the four magic numbers into package parameters, which is the difference between a design you can review and one you have to trust.
// usb3_capability_resolve -- which mechanism applies at which speed, and the
// one question software keeps answering with the wrong input.
//
// USB 3 did not replace USB 2. It added a second, parallel bus in the same
// cable (Chapter 20.2), and a device can end up operating on either one.
//
// USB 2 SuperSpeed
// flow host POLLS the device device is GIVEN CREDITS
// busy device answers NAK device sends NRDY, later ERDY
// unit load 100 mA 150 mA
// bursting no yes, up to bMaxBurst packets
// streams no yes, for bulk endpoints
// idle bus-wide SUSPEND per-link U1 / U2
//
// The SystemVerilog build separates the two capability answers into an enum
// and a bit, which makes the distinction visible at every call site: you
// cannot pass an op_mode_e where a "did it train" bit is wanted.
package usb3_cap_pkg;
// The USB 2 speed that was negotiated on the D+/D- pair.
typedef enum logic [1:0] {
SP_NONE = 2'd0, // nothing came up on USB 2 either
SP_LOW = 2'd1,
SP_FULL = 2'd2,
SP_HIGH = 2'd3
} usb2_speed_e;
// What is ACTUALLY running. OP_SUPER is reachable only through a trained
// SuperSpeed link -- never through a descriptor.
typedef enum logic [2:0] {
OP_NONE = 3'd0,
OP_LOW = 3'd1,
OP_FULL = 3'd2,
OP_HIGH = 3'd3,
OP_SUPER = 3'd4
} op_mode_e;
// The two ceilings on bus current, in unit loads.
parameter int unsigned UL_CEIL_SS = 6; // bus-powered, SuperSpeed
parameter int unsigned UL_CEIL_USB2 = 5; // bus-powered, USB 2
parameter int unsigned UL_CEIL_SELF = 1; // self-powered, either bus
parameter int unsigned UL_MA_SS = 150;
parameter int unsigned UL_MA_USB2 = 100;
endpackage
// THE QUESTION, AND THE WRONG INPUT
//
// "Is this a SuperSpeed device?" is not a question about the device. It is a
// question about THE LINK THAT WAS ACTUALLY ESTABLISHED.
//
// A SuperSpeed-capable device plugged into a USB 2 hub, or into a USB 3 port
// with a broken SuperSpeed pair, or through a cable missing the SuperSpeed
// wires, IS A USB 2 DEVICE. It reports SuperSpeed capability in its BOS
// descriptor, it is physically capable of SuperSpeed, and none of that is
// relevant: the link trained at high speed, so high-speed rules apply.
//
// Hence two outputs that are easy to conflate and must never be:
//
// ss_capable_reported what the descriptors CLAIM. Read from the device.
// ss_operating what is ACTUALLY running. Requires a capable
// device, a capable port, AND a trained link.
module usb3_capability_resolve
import usb3_cap_pkg::*;
(
input logic clk,
input logic rst_n,
// what the device claims
input logic dev_ss_capable,
input logic bos_present,
input logic [2:0] unit_loads_req,
input logic self_powered,
// what the link actually did
input logic port_ss_capable,
input logic ss_link_up,
input usb2_speed_e usb2_speed,
output logic ss_capable_reported,
output logic ss_operating,
output op_mode_e op_mode,
output logic credit_flow,
output logic async_notify,
output logic burst_allowed,
output logic streams_allowed,
output logic link_pm_u1u2,
output logic [7:0] unit_load_ma,
output logic [2:0] unit_loads_grant,
output logic [9:0] max_power_ma,
output logic power_capped,
output logic [31:0] n_superspeed,
output logic [31:0] n_fallback,
output logic [31:0] n_capped,
output logic [31:0] n_unconfigured
);
op_mode_e op_mode_c;
logic [2:0] ceiling;
// ---- The two capability answers, and they are NOT the same question ----
// What software can read out of the descriptors. Useful for logging and for
// telling a user "this device would be faster in a USB 3 port". Useful for
// NOTHING ELSE.
assign ss_capable_reported = dev_ss_capable && bos_present;
// What is actually running. All three terms are required.
assign ss_operating = dev_ss_capable && port_ss_capable && ss_link_up;
// ---- Operating mode: SuperSpeed if it trained, otherwise whatever USB 2
// ---- actually negotiated. NOT "high speed because it is a USB 3 device".
always_comb begin
if (ss_operating) op_mode_c = OP_SUPER;
else begin
case (usb2_speed)
SP_HIGH: op_mode_c = OP_HIGH;
SP_FULL: op_mode_c = OP_FULL;
SP_LOW: op_mode_c = OP_LOW;
default: op_mode_c = OP_NONE;
endcase
end
end
assign op_mode = op_mode_c;
// ---- Every mechanism, gated on ss_operating and never on the claim ----
assign credit_flow = ss_operating;
assign async_notify = ss_operating;
assign burst_allowed = ss_operating;
assign streams_allowed = ss_operating;
assign link_pm_u1u2 = ss_operating;
// ---- Power: the unit load is 150 mA on SuperSpeed, 100 mA on USB 2 ----
assign unit_load_ma = ss_operating ? 8'(UL_MA_SS) : 8'(UL_MA_USB2);
// Two independent ceilings apply and the LOWER one wins:
// a SELF-POWERED device may draw at most ONE unit load from the bus
// a BUS-POWERED device may draw 6 unit loads on SS, 5 on USB 2
always_comb begin
ceiling = ss_operating ? 3'(UL_CEIL_SS) : 3'(UL_CEIL_USB2);
if (self_powered && 3'(UL_CEIL_SELF) < ceiling)
ceiling = 3'(UL_CEIL_SELF);
end
assign unit_loads_grant = (unit_loads_req > ceiling) ? ceiling
: unit_loads_req;
assign power_capped = (unit_loads_req > ceiling);
// 6 unit loads x 150 mA = 900 mA, which is why this is 10 bits wide.
assign max_power_ma = 10'(unit_loads_grant) * 10'(unit_load_ma);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_superspeed <= '0;
n_fallback <= '0;
n_capped <= '0;
n_unconfigured <= '0;
end else begin
if (ss_operating) n_superspeed <= n_superspeed + 1;
// THE diagnostic worth having in real silicon: a device that could have
// run at SuperSpeed and did not. Every one of these is a cable, a hub
// or a training failure, and without this counter it is invisible --
// the device works, just at a twentieth of the speed.
if (ss_capable_reported && !ss_operating) n_fallback <= n_fallback + 1;
if (power_capped) n_capped <= n_capped + 1;
if (op_mode_c == OP_NONE) n_unconfigured <= n_unconfigured + 1;
end
end
endmodule7. VHDL-2008 Implementation
-- usb3_capability_resolve -- which mechanism applies at which speed, and the
-- one question software keeps answering with the wrong input.
--
-- USB 3 did not replace USB 2. It added a second, parallel bus in the same
-- cable (Chapter 20.2), and a device can end up operating on either one.
--
-- USB 2 SuperSpeed
-- flow host POLLS the device device is GIVEN CREDITS
-- busy device answers NAK device sends NRDY, later ERDY
-- unit load 100 mA 150 mA
-- bursting no yes, up to bMaxBurst packets
-- streams no yes, for bulk endpoints
-- idle bus-wide SUSPEND per-link U1 / U2
--
-- VHDL's type system is the reason this version is worth reading alongside
-- the other two: op_mode_t is an enumerated type whose OP_SUPER value cannot
-- be produced by accident, and the two capability answers are separate
-- signals of separate meaning rather than two bits that look alike.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb3_cap_pkg is
-- The USB 2 speed that was negotiated on the D+/D- pair.
type usb2_speed_t is (SP_NONE, SP_LOW, SP_FULL, SP_HIGH);
-- What is ACTUALLY running. OP_SUPER is reachable only through a trained
-- SuperSpeed link -- never through a descriptor.
type op_mode_t is (OP_NONE, OP_LOW, OP_FULL, OP_HIGH, OP_SUPER);
-- The two ceilings on bus current, in unit loads.
constant UL_CEIL_SS : natural := 6; -- bus-powered, SuperSpeed
constant UL_CEIL_USB2 : natural := 5; -- bus-powered, USB 2
constant UL_CEIL_SELF : natural := 1; -- self-powered, either bus
constant UL_MA_SS : natural := 150;
constant UL_MA_USB2 : natural := 100;
function speed_decode(s : std_logic_vector(1 downto 0)) return usb2_speed_t;
function mode_code(m : op_mode_t) return std_logic_vector;
end package;
package body usb3_cap_pkg is
function speed_decode(s : std_logic_vector(1 downto 0)) return usb2_speed_t is
begin
case s is
when "00" => return SP_NONE;
when "01" => return SP_LOW;
when "10" => return SP_FULL;
when others => return SP_HIGH;
end case;
end function;
function mode_code(m : op_mode_t) return std_logic_vector is
begin
return std_logic_vector(to_unsigned(op_mode_t'pos(m), 3));
end function;
end package body;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb3_cap_pkg.all;
-- THE QUESTION, AND THE WRONG INPUT
--
-- "Is this a SuperSpeed device?" is not a question about the device. It is a
-- question about THE LINK THAT WAS ACTUALLY ESTABLISHED.
--
-- A SuperSpeed-capable device plugged into a USB 2 hub, or into a USB 3 port
-- with a broken SuperSpeed pair, or through a cable missing the SuperSpeed
-- wires, IS A USB 2 DEVICE. It reports SuperSpeed capability in its BOS
-- descriptor, it is physically capable of SuperSpeed, and none of that is
-- relevant: the link trained at high speed, so high-speed rules apply.
--
-- Hence two outputs that are easy to conflate and must never be:
--
-- ss_capable_reported what the descriptors CLAIM. Read from the device.
-- ss_operating what is ACTUALLY running. Requires a capable
-- device, a capable port, AND a trained link.
entity usb3_capability_resolve is
port (
clk : in std_logic;
rst_n : in std_logic;
-- what the device claims
dev_ss_capable : in std_logic;
bos_present : in std_logic;
unit_loads_req : in std_logic_vector(2 downto 0);
self_powered : in std_logic;
-- what the link actually did
port_ss_capable : in std_logic;
ss_link_up : in std_logic;
usb2_speed : in std_logic_vector(1 downto 0);
ss_capable_reported : out std_logic;
ss_operating : out std_logic;
op_mode : out std_logic_vector(2 downto 0);
credit_flow : out std_logic;
async_notify : out std_logic;
burst_allowed : out std_logic;
streams_allowed : out std_logic;
link_pm_u1u2 : out std_logic;
unit_load_ma : out std_logic_vector(7 downto 0);
unit_loads_grant : out std_logic_vector(2 downto 0);
max_power_ma : out std_logic_vector(9 downto 0);
power_capped : out std_logic;
n_superspeed : out std_logic_vector(31 downto 0);
n_fallback : out std_logic_vector(31 downto 0);
n_capped : out std_logic_vector(31 downto 0);
n_unconfigured : out std_logic_vector(31 downto 0)
);
end entity;
architecture rtl of usb3_capability_resolve is
signal rep : std_logic;
signal oper : std_logic;
signal mode : op_mode_t;
signal ul_ma : natural range 0 to 255;
signal ceil_u : natural range 0 to 7;
signal grant : natural range 0 to 7;
signal capped : std_logic;
signal ss_r, fb_r, cap_r, unc_r : unsigned(31 downto 0) := (others => '0');
begin
-- ---- The two capability answers, and they are NOT the same question ----
-- What software can read out of the descriptors. Useful for logging and for
-- telling a user "this device would be faster in a USB 3 port". Useful for
-- NOTHING ELSE.
rep <= dev_ss_capable and bos_present;
-- What is actually running. All three terms are required.
oper <= dev_ss_capable and port_ss_capable and ss_link_up;
ss_capable_reported <= rep;
ss_operating <= oper;
-- ---- Operating mode: SuperSpeed if it trained, otherwise whatever USB 2
-- ---- actually negotiated. NOT "high speed because it is a USB 3 device".
mode_sel : process (oper, usb2_speed)
begin
if oper = '1' then
mode <= OP_SUPER;
else
case speed_decode(usb2_speed) is
when SP_HIGH => mode <= OP_HIGH;
when SP_FULL => mode <= OP_FULL;
when SP_LOW => mode <= OP_LOW;
when SP_NONE => mode <= OP_NONE;
end case;
end if;
end process;
op_mode <= mode_code(mode);
-- ---- Every mechanism, gated on ss_operating and never on the claim ----
credit_flow <= oper;
async_notify <= oper;
burst_allowed <= oper;
streams_allowed <= oper;
link_pm_u1u2 <= oper;
-- ---- Power: the unit load is 150 mA on SuperSpeed, 100 mA on USB 2 ----
ul_ma <= UL_MA_SS when oper = '1' else UL_MA_USB2;
-- Two independent ceilings apply and the LOWER one wins:
-- a SELF-POWERED device may draw at most ONE unit load from the bus
-- a BUS-POWERED device may draw 6 unit loads on SS, 5 on USB 2
ceiling_sel : process (oper, self_powered)
variable c : natural range 0 to 7;
begin
if oper = '1' then
c := UL_CEIL_SS;
else
c := UL_CEIL_USB2;
end if;
if self_powered = '1' and UL_CEIL_SELF < c then
c := UL_CEIL_SELF;
end if;
ceil_u <= c;
end process;
grant_sel : process (unit_loads_req, ceil_u)
variable req : natural range 0 to 7;
begin
req := to_integer(unsigned(unit_loads_req));
if req > ceil_u then
grant <= ceil_u;
capped <= '1';
else
grant <= req;
capped <= '0';
end if;
end process;
unit_loads_grant <= std_logic_vector(to_unsigned(grant, 3));
unit_load_ma <= std_logic_vector(to_unsigned(ul_ma, 8));
power_capped <= capped;
-- 6 unit loads x 150 mA = 900 mA, which is why this is 10 bits wide. The
-- multiply is done in the integer domain deliberately: an unsigned * natural
-- in VHDL returns a vector as wide as the two operands combined, which is a
-- runtime length error waiting to happen on an output port.
max_power_ma <= std_logic_vector(to_unsigned(grant * ul_ma, 10));
counters : process (clk, rst_n)
begin
if rst_n = '0' then
ss_r <= (others => '0');
fb_r <= (others => '0');
cap_r <= (others => '0');
unc_r <= (others => '0');
elsif rising_edge(clk) then
if oper = '1' then
ss_r <= ss_r + 1;
end if;
-- THE diagnostic worth having in real silicon: a device that could have
-- run at SuperSpeed and did not. Every one of these is a cable, a hub
-- or a training failure, and without this counter it is invisible --
-- the device works, just at a twentieth of the speed.
if rep = '1' and oper = '0' then
fb_r <= fb_r + 1;
end if;
if capped = '1' then
cap_r <= cap_r + 1;
end if;
if mode = OP_NONE then
unc_r <= unc_r + 1;
end if;
end if;
end process;
n_superspeed <= std_logic_vector(ss_r);
n_fallback <= std_logic_vector(fb_r);
n_capped <= std_logic_vector(cap_r);
n_unconfigured <= std_logic_vector(unc_r);
end architecture;The max_power_ma assignment carries a comment that is worth reading twice, because the trap behind it cost real time earlier in this series:
-- WRONG: unsigned * natural in VHDL returns a vector as WIDE AS THE TWO
-- OPERANDS COMBINED, so this is a 3 + 8 = 11-bit result assigned to a
-- 10-bit port, and nvc raises a length error at RUNTIME, not at analysis.
max_power_ma <= std_logic_vector(unsigned(unit_loads_grant) * ul_ma);
-- RIGHT: do the arithmetic in the integer domain and size the result once.
max_power_ma <= std_logic_vector(to_unsigned(grant * ul_ma, 10));This is the category of VHDL error that makes people say VHDL is verbose. It is not verbosity — it is that the language refuses to silently truncate, and the price of that refusal is having to say how wide you meant.
8. Seeing the Same Device Twice
The clearest way to understand this module is to watch one device, unchanged, in two different ports.
One device, two ports — and every rule changes
usb3_capability_resolve — the same device in two ports
10 cyclesss_capable_reported is the one signal that does not move. That is the whole lesson in one row: the device's claim is a constant property of the device, and every rule that matters is a property of the link.
9. The Testbenches
Each suite sweeps the complete 1024-point space, runs seven named directed scenarios, and then 40 000 randomised cycles against a reference model built the other way round — an explicit min over both ceilings where the design narrows conditionally, and a case on the speed where the design uses a ternary chain.
9.1 Verilog testbench
`timescale 1ns/1ps
module tb_cr_v;
reg clk=0, rst_n=0;
reg dss=0, bos=0, sp=0, pss=0, slu=0;
reg [2:0] ulr=0;
reg [1:0] u2s=0;
wire ss_capable_reported, ss_operating;
wire [2:0] op_mode;
wire credit_flow, async_notify, burst_allowed, streams_allowed, link_pm_u1u2;
wire [7:0] unit_load_ma;
wire [2:0] unit_loads_grant;
wire [9:0] max_power_ma;
wire power_capped;
wire [31:0] n_superspeed, n_fallback, n_capped, n_unconfigured;
always #5 clk=~clk;
usb3_capability_resolve dut (
.clk(clk), .rst_n(rst_n), .dev_ss_capable(dss), .bos_present(bos),
.unit_loads_req(ulr), .self_powered(sp), .port_ss_capable(pss),
.ss_link_up(slu), .usb2_speed(u2s),
.ss_capable_reported(ss_capable_reported), .ss_operating(ss_operating),
.op_mode(op_mode), .credit_flow(credit_flow), .async_notify(async_notify),
.burst_allowed(burst_allowed), .streams_allowed(streams_allowed),
.link_pm_u1u2(link_pm_u1u2), .unit_load_ma(unit_load_ma),
.unit_loads_grant(unit_loads_grant), .max_power_ma(max_power_ma),
.power_capped(power_capped), .n_superspeed(n_superspeed),
.n_fallback(n_fallback), .n_capped(n_capped),
.n_unconfigured(n_unconfigured));
localparam [1:0] SP_NONE=0, SP_LOW=1, SP_FULL=2, SP_HIGH=3;
localparam [2:0] OP_NONE=0, OP_LOW=1, OP_FULL=2, OP_HIGH=3, OP_SUPER=4;
integer errors=0, i, a, b, c, d, e, f, g;
integer n_exh=0;
integer n_op [0:4];
integer n_ss=0, n_fb=0, n_cap=0, n_selfcap=0, n_claim_only=0;
integer m_ss, m_fb, m_cap, m_unc;
task check(input cond, input [639:0] msg);
begin if (!cond) begin errors=errors+1;
if (errors <= 25)
$display(" FAIL: %0s (dss=%b bos=%b pss=%b slu=%b u2s=%0d ulr=%0d sp=%b | op=%0d ssop=%b grant=%0d ul=%0d pwr=%0d, t=%0t)",
msg, dss, bos, pss, slu, u2s, ulr, sp, op_mode, ss_operating,
unit_loads_grant, unit_load_ma, max_power_ma, $time);
end end
endtask
task check_comb;
reg e_rep, e_op;
integer e_mode, e_ul, e_ceil, e_grant, e_pwr;
reg e_cap;
begin
// The reference model is deliberately built the other way round: it
// computes the ceiling with an explicit min() over both limits rather
// than a nested ternary, and derives the mode with a case.
e_rep = dss && bos;
e_op = dss && pss && slu;
case (u2s)
SP_HIGH: e_mode = OP_HIGH;
SP_FULL: e_mode = OP_FULL;
SP_LOW: e_mode = OP_LOW;
default: e_mode = OP_NONE;
endcase
if (e_op) e_mode = OP_SUPER;
e_ul = e_op ? 150 : 100;
// both ceilings, lowest wins
e_ceil = e_op ? 6 : 5;
if (sp && 1 < e_ceil) e_ceil = 1;
e_grant = (ulr > e_ceil) ? e_ceil : ulr;
e_cap = (ulr > e_ceil);
e_pwr = e_grant * e_ul;
check(ss_capable_reported === e_rep,
"ss_capable_reported is the DESCRIPTOR claim");
check(ss_operating === e_op,
"ss_operating needs device AND port AND a trained link");
check(op_mode === e_mode[2:0], "op_mode matches the model");
check(unit_load_ma === e_ul[7:0], "unit load is 150 on SS, 100 on USB 2");
check(unit_loads_grant === e_grant[2:0], "unit loads granted match");
check(power_capped === e_cap, "power_capped matches the model");
check(max_power_ma === e_pwr[9:0], "max_power_ma matches the model");
// Every mechanism follows ss_operating, none follows the claim.
check(credit_flow === e_op, "credit flow follows ss_operating");
check(async_notify === e_op, "ERDY/NRDY follows ss_operating");
check(burst_allowed === e_op, "bursting follows ss_operating");
check(streams_allowed === e_op, "streams follow ss_operating");
check(link_pm_u1u2 === e_op, "U1/U2 follows ss_operating");
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE property. Not one mechanism may be enabled by the claim alone.
if (ss_capable_reported && !ss_operating) begin
check(!credit_flow && !async_notify && !burst_allowed
&& !streams_allowed && !link_pm_u1u2,
"a SuperSpeed mechanism was enabled on a USB 2 link");
check(unit_load_ma === 8'd100,
"a 150 mA unit load was granted on a USB 2 link");
check(op_mode !== OP_SUPER,
"op_mode says SuperSpeed but the link never trained");
end
// 2. SuperSpeed operation is impossible without all three terms.
check(!ss_operating || (dss && pss && slu),
"ss_operating asserted with a term missing");
// 3. A self-powered device never draws more than one unit load.
if (sp)
check(unit_loads_grant <= 3'd1,
"a self-powered device was granted more than one unit load");
// 4. The grant never exceeds the request -- a cap only ever reduces.
check(unit_loads_grant <= ulr,
"more unit loads were granted than were requested");
// 5. The USB 2 ceiling is 5 unit loads (500 mA), never 6.
if (!ss_operating)
check(unit_loads_grant <= 3'd5,
"more than 500 mA granted on a USB 2 link");
// 6. Power is exactly grant x unit load -- no rounding, no slack.
check(max_power_ma === (unit_loads_grant * unit_load_ma),
"max_power_ma is not grant x unit load");
// 7. An unconfigured port grants nothing and runs nothing.
if (op_mode === OP_NONE)
check(!credit_flow && !burst_allowed && !streams_allowed,
"mechanisms enabled on a port with no link at all");
if (e_mode >= 0 && e_mode <= 4) n_op[e_mode] = n_op[e_mode] + 1;
if (e_op) n_ss = n_ss + 1;
if (e_rep && !e_op) n_fb = n_fb + 1;
if (e_cap) n_cap = n_cap + 1;
if (sp && e_cap) n_selfcap = n_selfcap + 1;
if (e_rep && !e_op && (u2s != SP_NONE)) n_claim_only = n_claim_only + 1;
end
endtask
task step;
begin
#1;
check_comb;
if (ss_operating) m_ss = m_ss + 1;
if (ss_capable_reported && !ss_operating) m_fb = m_fb + 1;
if (power_capped) m_cap = m_cap + 1;
if (op_mode === OP_NONE) m_unc = m_unc + 1;
@(posedge clk); #1;
check(n_superspeed === m_ss[31:0], "n_superspeed matches the model");
check(n_fallback === m_fb[31:0], "n_fallback matches the model");
check(n_capped === m_cap[31:0], "n_capped matches the model");
check(n_unconfigured === m_unc[31:0], "n_unconfigured matches the model");
end
endtask
task hard_reset;
begin
rst_n=0; dss=0; bos=0; sp=0; pss=0; slu=0; ulr=0; u2s=0;
@(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
m_ss=0; m_fb=0; m_cap=0; m_unc=0;
end
endtask
initial begin
for (i=0;i<5;i=i+1) n_op[i]=0;
hard_reset;
check(op_mode === OP_NONE, "nothing connected, no operating mode");
check(!ss_operating, "and nothing is running at SuperSpeed");
// ===== A. EXHAUSTIVE over the whole resolution =====
// dev_ss_capable x bos_present x port_ss_capable x ss_link_up
// x usb2_speed(4) x unit_loads_req(8) x self_powered
// = 2 x 2 x 2 x 2 x 4 x 8 x 2 = 1024 points, the complete input space.
for (a=0;a<2;a=a+1)
for (b=0;b<2;b=b+1)
for (c=0;c<2;c=c+1)
for (d=0;d<2;d=d+1)
for (e=0;e<4;e=e+1)
for (f=0;f<8;f=f+1)
for (g=0;g<2;g=g+1) begin
dss=a[0]; bos=b[0]; pss=c[0]; slu=d[0];
u2s=e[1:0]; ulr=f[2:0]; sp=g[0];
step;
n_exh = n_exh + 1;
end
$display(" exhaustive capability-resolution sweep: %0d of %0d points verified",
n_exh, 2*2*2*2*4*8*2);
// ===== B. directed: the scenarios that get argued about =====
hard_reset;
// 1. A SuperSpeed device in a SuperSpeed port. Everything on.
dss=1; bos=1; pss=1; slu=1; u2s=SP_HIGH; ulr=6; sp=0; step;
check(op_mode === OP_SUPER, "a trained SS link operates at SuperSpeed");
check(credit_flow && async_notify && burst_allowed && streams_allowed,
"and every SuperSpeed mechanism is available");
check(unit_load_ma === 8'd150, "the unit load is 150 mA");
check(unit_loads_grant === 3'd6, "6 unit loads are allowed");
check(max_power_ma === 10'd900, "which is 900 mA");
check(!power_capped, "and nothing was capped");
// 2. THE case. The SAME device, behind a USB 2 hub. It is a USB 2 device.
dss=1; bos=1; pss=0; slu=0; u2s=SP_HIGH; ulr=6; sp=0; step;
check(ss_capable_reported,
"the descriptors still claim SuperSpeed capability");
check(!ss_operating, "but nothing SuperSpeed is running");
check(op_mode === OP_HIGH, "it is a HIGH SPEED device now");
check(!credit_flow, "the host must POLL it");
check(!async_notify, "it answers NAK, not NRDY/ERDY");
check(!burst_allowed && !streams_allowed, "no bursting, no streams");
check(!link_pm_u1u2, "and idle means bus suspend, not U1/U2");
check(unit_load_ma === 8'd100, "its unit load is 100 mA");
check(unit_loads_grant === 3'd5, "capped at 5 unit loads");
check(max_power_ma === 10'd500, "so 500 mA, not 900");
check(power_capped, "and the request WAS capped");
// 3. A SuperSpeed port, a capable device, but training FAILED.
// Identical outcome to case 2 -- which is the point.
dss=1; bos=1; pss=1; slu=0; u2s=SP_HIGH; ulr=6; sp=0; step;
check(!ss_operating,
"a capable device in a capable port still is not SuperSpeed if training failed");
check(op_mode === OP_HIGH, "it fell back to high speed");
check(unit_load_ma === 8'd100, "with a USB 2 unit load");
// 4. A device that claims SuperSpeed with NO BOS descriptor.
dss=1; bos=0; pss=1; slu=1; u2s=SP_HIGH; ulr=2; sp=0; step;
check(!ss_capable_reported,
"with no BOS descriptor there is no reported capability");
check(ss_operating,
"yet the link trained, so SuperSpeed IS operating -- the link decides");
// 5. A self-powered SuperSpeed device asking for 6 unit loads.
dss=1; bos=1; pss=1; slu=1; u2s=SP_HIGH; ulr=6; sp=1; step;
check(unit_loads_grant === 3'd1,
"a self-powered device gets ONE unit load whatever it asks for");
check(max_power_ma === 10'd150, "which is 150 mA on SuperSpeed");
check(power_capped, "and the request was capped");
// 6. A low-speed device: no SuperSpeed anything, 100 mA unit load.
dss=0; bos=0; pss=1; slu=0; u2s=SP_LOW; ulr=1; sp=0; step;
check(op_mode === OP_LOW, "a low speed device operates at low speed");
check(unit_load_ma === 8'd100, "with a 100 mA unit load");
check(max_power_ma === 10'd100, "and 100 mA total");
// 7. Nothing plugged in at all.
dss=1; bos=1; pss=1; slu=0; u2s=SP_NONE; ulr=4; sp=0; step;
check(op_mode === OP_NONE, "no link of either kind means no mode");
check(!credit_flow && !burst_allowed, "and no mechanisms at all");
// ===== C. randomised =====
for (i=0;i<40000;i=i+1) begin
dss={$random}%2; bos={$random}%2; pss={$random}%2; slu={$random}%2;
u2s={$random}%4; ulr={$random}%8; sp=({$random}%4)==0;
step;
end
for (i=0;i<5;i=i+1)
check(n_op[i] > 0, "every operating mode was reached");
check(n_ss > 1000, "SuperSpeed operation was reached often");
check(n_fb > 1000, "the SS-capable-but-not-operating case was reached often");
check(n_selfcap > 100, "self-powered devices were capped");
check(n_claim_only > 1000,
"the claim-without-operation case was reached with a live USB 2 link");
$display("");
$display(" REACH: exhaustive=%0d | modes: none=%0d low=%0d full=%0d high=%0d super=%0d",
n_exh, n_op[0], n_op[1], n_op[2], n_op[3], n_op[4]);
$display(" CASES: ss-operating=%0d fallback=%0d capped=%0d self-capped=%0d claim-only-live=%0d",
n_ss, n_fb, n_cap, n_selfcap, n_claim_only);
$display(" COUNTERS: superspeed=%0d fallback=%0d capped=%0d unconfigured=%0d",
n_superspeed, n_fallback, n_capped, n_unconfigured);
$display(" [Verilog] usb3_capability_resolve: %0d errors", errors);
$display(" [Verilog] %0s", errors==0 ? "PASS" : "FAIL");
$display("");
$finish;
end
endmodule9.2 SystemVerilog testbench
`timescale 1ns/1ps
module tb_cr_sv;
import usb3_cap_pkg::*;
logic clk=0, rst_n=0;
logic dss=0, bos=0, sp=0, pss=0, slu=0;
logic [2:0] ulr=0;
usb2_speed_e u2s = SP_NONE;
logic ss_capable_reported, ss_operating;
op_mode_e op_mode;
logic credit_flow, async_notify, burst_allowed, streams_allowed, link_pm_u1u2;
logic [7:0] unit_load_ma;
logic [2:0] unit_loads_grant;
logic [9:0] max_power_ma;
logic power_capped;
logic [31:0] n_superspeed, n_fallback, n_capped, n_unconfigured;
always #5 clk=~clk;
usb3_capability_resolve dut (
.clk, .rst_n, .dev_ss_capable(dss), .bos_present(bos),
.unit_loads_req(ulr), .self_powered(sp), .port_ss_capable(pss),
.ss_link_up(slu), .usb2_speed(u2s), .ss_capable_reported, .ss_operating,
.op_mode, .credit_flow, .async_notify, .burst_allowed, .streams_allowed,
.link_pm_u1u2, .unit_load_ma, .unit_loads_grant, .max_power_ma,
.power_capped, .n_superspeed, .n_fallback, .n_capped, .n_unconfigured);
int errors=0, i, a, b, c, d, e, f, g;
int n_exh=0;
int n_op [5];
int n_ss=0, n_fb=0, n_cap=0, n_selfcap=0, n_claim_only=0;
int m_ss, m_fb, m_cap, m_unc;
// Icarus seeds $random and $urandom IDENTICALLY, so an unseeded
// SystemVerilog run replays the Verilog run's stimulus exactly and the two
// columns stop being independent evidence. Seeding one of them explicitly
// is what makes the randomised phases three separate samples.
int urandom_seed = 20251;
// Icarus will not call .name() on a net, so the enum output is copied into
// a variable of the same type before being printed.
task automatic check(input bit cond, input string msg);
op_mode_e om_v;
if (!cond) begin
errors++;
om_v = op_mode;
if (errors <= 25)
$display(" FAIL: %0s (dss=%b bos=%b pss=%b slu=%b u2s=%s ulr=%0d sp=%b | op=%s ssop=%b grant=%0d ul=%0d pwr=%0d, t=%0t)",
msg, dss, bos, pss, slu, u2s.name(), ulr, sp, om_v.name(),
ss_operating, unit_loads_grant, unit_load_ma, max_power_ma,
$time);
end
endtask
task automatic check_comb;
bit e_rep, e_op, e_cap;
op_mode_e e_mode;
int e_ul, e_ceil, e_grant, e_pwr;
begin
// The reference model is deliberately built the other way round: it
// computes the ceiling with an explicit min over both limits rather
// than the design's conditional narrowing.
e_rep = dss && bos;
e_op = dss && pss && slu;
case (u2s)
SP_HIGH: e_mode = OP_HIGH;
SP_FULL: e_mode = OP_FULL;
SP_LOW: e_mode = OP_LOW;
default: e_mode = OP_NONE;
endcase
if (e_op) e_mode = OP_SUPER;
e_ul = e_op ? int'(UL_MA_SS) : int'(UL_MA_USB2);
e_ceil = e_op ? int'(UL_CEIL_SS) : int'(UL_CEIL_USB2);
if (sp && int'(UL_CEIL_SELF) < e_ceil) e_ceil = int'(UL_CEIL_SELF);
e_grant = (int'(ulr) > e_ceil) ? e_ceil : int'(ulr);
e_cap = (int'(ulr) > e_ceil);
e_pwr = e_grant * e_ul;
check(ss_capable_reported === e_rep,
"ss_capable_reported is the DESCRIPTOR claim");
check(ss_operating === e_op,
"ss_operating needs device AND port AND a trained link");
check(op_mode === e_mode, "op_mode matches the model");
check(unit_load_ma === 8'(e_ul), "unit load is 150 on SS, 100 on USB 2");
check(unit_loads_grant === 3'(e_grant), "unit loads granted match");
check(power_capped === e_cap, "power_capped matches the model");
check(max_power_ma === 10'(e_pwr), "max_power_ma matches the model");
// Every mechanism follows ss_operating, none follows the claim.
check(credit_flow === e_op, "credit flow follows ss_operating");
check(async_notify === e_op, "ERDY/NRDY follows ss_operating");
check(burst_allowed === e_op, "bursting follows ss_operating");
check(streams_allowed === e_op, "streams follow ss_operating");
check(link_pm_u1u2 === e_op, "U1/U2 follows ss_operating");
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE property. Not one mechanism may be enabled by the claim alone.
if (ss_capable_reported && !ss_operating) begin
check(!credit_flow && !async_notify && !burst_allowed
&& !streams_allowed && !link_pm_u1u2,
"a SuperSpeed mechanism was enabled on a USB 2 link");
check(unit_load_ma === 8'd100,
"a 150 mA unit load was granted on a USB 2 link");
check(op_mode !== OP_SUPER,
"op_mode says SuperSpeed but the link never trained");
end
// 2. SuperSpeed operation is impossible without all three terms.
check(!ss_operating || (dss && pss && slu),
"ss_operating asserted with a term missing");
// 3. A self-powered device never draws more than one unit load.
if (sp)
check(unit_loads_grant <= 3'd1,
"a self-powered device was granted more than one unit load");
// 4. The grant never exceeds the request -- a cap only ever reduces.
check(unit_loads_grant <= ulr,
"more unit loads were granted than were requested");
// 5. The USB 2 ceiling is 5 unit loads (500 mA), never 6.
if (!ss_operating)
check(unit_loads_grant <= 3'd5,
"more than 500 mA granted on a USB 2 link");
// 6. Power is exactly grant x unit load -- no rounding, no slack.
check(max_power_ma === 10'(unit_loads_grant) * 10'(unit_load_ma),
"max_power_ma is not grant x unit load");
// 7. An unconfigured port grants nothing and runs nothing.
if (op_mode === OP_NONE)
check(!credit_flow && !burst_allowed && !streams_allowed,
"mechanisms enabled on a port with no link at all");
n_op[int'(e_mode)] = n_op[int'(e_mode)] + 1;
if (e_op) n_ss++;
if (e_rep && !e_op) n_fb++;
if (e_cap) n_cap++;
if (sp && e_cap) n_selfcap++;
if (e_rep && !e_op && (u2s != SP_NONE)) n_claim_only++;
end
endtask
task automatic step;
begin
#1;
check_comb;
if (ss_operating) m_ss++;
if (ss_capable_reported && !ss_operating) m_fb++;
if (power_capped) m_cap++;
if (op_mode === OP_NONE) m_unc++;
@(posedge clk); #1;
check(n_superspeed === 32'(m_ss), "n_superspeed matches the model");
check(n_fallback === 32'(m_fb), "n_fallback matches the model");
check(n_capped === 32'(m_cap), "n_capped matches the model");
check(n_unconfigured === 32'(m_unc), "n_unconfigured matches the model");
end
endtask
task automatic hard_reset;
begin
rst_n=0; dss=0; bos=0; sp=0; pss=0; slu=0; ulr=0; u2s=SP_NONE;
@(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
m_ss=0; m_fb=0; m_cap=0; m_unc=0;
end
endtask
initial begin
void'($urandom(urandom_seed));
foreach (n_op[i]) n_op[i]=0;
hard_reset;
check(op_mode === OP_NONE, "nothing connected, no operating mode");
check(!ss_operating, "and nothing is running at SuperSpeed");
// ===== A. EXHAUSTIVE over the whole resolution =====
// dev_ss_capable x bos_present x port_ss_capable x ss_link_up
// x usb2_speed(4) x unit_loads_req(8) x self_powered
// = 2 x 2 x 2 x 2 x 4 x 8 x 2 = 1024 points, the complete input space.
for (a=0;a<2;a++)
for (b=0;b<2;b++)
for (c=0;c<2;c++)
for (d=0;d<2;d++)
for (e=0;e<4;e++)
for (f=0;f<8;f++)
for (g=0;g<2;g++) begin
dss=a[0]; bos=b[0]; pss=c[0]; slu=d[0];
u2s=usb2_speed_e'(e[1:0]); ulr=f[2:0]; sp=g[0];
step;
n_exh++;
end
$display(" exhaustive capability-resolution sweep: %0d of %0d points verified",
n_exh, 2*2*2*2*4*8*2);
// ===== B. directed: the scenarios that get argued about =====
hard_reset;
// 1. A SuperSpeed device in a SuperSpeed port. Everything on.
dss=1; bos=1; pss=1; slu=1; u2s=SP_HIGH; ulr=6; sp=0; step;
check(op_mode === OP_SUPER, "a trained SS link operates at SuperSpeed");
check(credit_flow && async_notify && burst_allowed && streams_allowed,
"and every SuperSpeed mechanism is available");
check(unit_load_ma === 8'd150, "the unit load is 150 mA");
check(unit_loads_grant === 3'd6, "6 unit loads are allowed");
check(max_power_ma === 10'd900, "which is 900 mA");
check(!power_capped, "and nothing was capped");
// 2. THE case. The SAME device, behind a USB 2 hub. It is a USB 2 device.
dss=1; bos=1; pss=0; slu=0; u2s=SP_HIGH; ulr=6; sp=0; step;
check(ss_capable_reported,
"the descriptors still claim SuperSpeed capability");
check(!ss_operating, "but nothing SuperSpeed is running");
check(op_mode === OP_HIGH, "it is a HIGH SPEED device now");
check(!credit_flow, "the host must POLL it");
check(!async_notify, "it answers NAK, not NRDY/ERDY");
check(!burst_allowed && !streams_allowed, "no bursting, no streams");
check(!link_pm_u1u2, "and idle means bus suspend, not U1/U2");
check(unit_load_ma === 8'd100, "its unit load is 100 mA");
check(unit_loads_grant === 3'd5, "capped at 5 unit loads");
check(max_power_ma === 10'd500, "so 500 mA, not 900");
check(power_capped, "and the request WAS capped");
// 3. A SuperSpeed port, a capable device, but training FAILED.
// Identical outcome to case 2 -- which is the point.
dss=1; bos=1; pss=1; slu=0; u2s=SP_HIGH; ulr=6; sp=0; step;
check(!ss_operating,
"a capable device in a capable port still is not SuperSpeed if training failed");
check(op_mode === OP_HIGH, "it fell back to high speed");
check(unit_load_ma === 8'd100, "with a USB 2 unit load");
// 4. A device that claims SuperSpeed with NO BOS descriptor.
dss=1; bos=0; pss=1; slu=1; u2s=SP_HIGH; ulr=2; sp=0; step;
check(!ss_capable_reported,
"with no BOS descriptor there is no reported capability");
check(ss_operating,
"yet the link trained, so SuperSpeed IS operating -- the link decides");
// 5. A self-powered SuperSpeed device asking for 6 unit loads.
dss=1; bos=1; pss=1; slu=1; u2s=SP_HIGH; ulr=6; sp=1; step;
check(unit_loads_grant === 3'd1,
"a self-powered device gets ONE unit load whatever it asks for");
check(max_power_ma === 10'd150, "which is 150 mA on SuperSpeed");
check(power_capped, "and the request was capped");
// 6. A low-speed device: no SuperSpeed anything, 100 mA unit load.
dss=0; bos=0; pss=1; slu=0; u2s=SP_LOW; ulr=1; sp=0; step;
check(op_mode === OP_LOW, "a low speed device operates at low speed");
check(unit_load_ma === 8'd100, "with a 100 mA unit load");
check(max_power_ma === 10'd100, "and 100 mA total");
// 7. Nothing plugged in at all.
dss=1; bos=1; pss=1; slu=0; u2s=SP_NONE; ulr=4; sp=0; step;
check(op_mode === OP_NONE, "no link of either kind means no mode");
check(!credit_flow && !burst_allowed, "and no mechanisms at all");
// ===== C. randomised =====
for (i=0;i<40000;i++) begin
dss=$urandom%2; bos=$urandom%2; pss=$urandom%2; slu=$urandom%2;
u2s=usb2_speed_e'($urandom%4); ulr=$urandom%8; sp=($urandom%4)==0;
step;
end
foreach (n_op[i]) check(n_op[i] > 0, "every operating mode was reached");
check(n_ss > 1000, "SuperSpeed operation was reached often");
check(n_fb > 1000, "the SS-capable-but-not-operating case was reached often");
check(n_selfcap > 100, "self-powered devices were capped");
check(n_claim_only > 1000,
"the claim-without-operation case was reached with a live USB 2 link");
$display("");
$display(" REACH: exhaustive=%0d | modes: none=%0d low=%0d full=%0d high=%0d super=%0d",
n_exh, n_op[0], n_op[1], n_op[2], n_op[3], n_op[4]);
$display(" CASES: ss-operating=%0d fallback=%0d capped=%0d self-capped=%0d claim-only-live=%0d",
n_ss, n_fb, n_cap, n_selfcap, n_claim_only);
$display(" COUNTERS: superspeed=%0d fallback=%0d capped=%0d unconfigured=%0d",
n_superspeed, n_fallback, n_capped, n_unconfigured);
$display(" [SystemVerilog] usb3_capability_resolve: %0d errors", errors);
$display(" [SystemVerilog] %0s", errors==0 ? "PASS" : "FAIL");
$display("");
$finish;
end
endmodule9.3 VHDL testbench
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.usb3_cap_pkg.all;
entity tb_cr_vhdl is
end entity;
architecture sim of tb_cr_vhdl is
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal dss, bos, sp, pss, slu : std_logic := '0';
signal ulr : std_logic_vector(2 downto 0) := (others => '0');
signal u2s : std_logic_vector(1 downto 0) := (others => '0');
signal ss_capable_reported, ss_operating : std_logic;
signal op_mode : std_logic_vector(2 downto 0);
signal credit_flow, async_notify, burst_allowed : std_logic;
signal streams_allowed, link_pm_u1u2 : std_logic;
signal unit_load_ma : std_logic_vector(7 downto 0);
signal unit_loads_grant : std_logic_vector(2 downto 0);
signal max_power_ma : std_logic_vector(9 downto 0);
signal power_capped : std_logic;
signal n_superspeed, n_fallback, n_capped, n_unconfigured
: std_logic_vector(31 downto 0);
signal running : boolean := true;
type cnt5_t is array (0 to 4) of integer;
begin
clk <= not clk after 5 ns when running else '0';
dut : entity work.usb3_capability_resolve
port map (clk => clk, rst_n => rst_n, dev_ss_capable => dss,
bos_present => bos, unit_loads_req => ulr, self_powered => sp,
port_ss_capable => pss, ss_link_up => slu, usb2_speed => u2s,
ss_capable_reported => ss_capable_reported,
ss_operating => ss_operating, op_mode => op_mode,
credit_flow => credit_flow, async_notify => async_notify,
burst_allowed => burst_allowed,
streams_allowed => streams_allowed,
link_pm_u1u2 => link_pm_u1u2, unit_load_ma => unit_load_ma,
unit_loads_grant => unit_loads_grant,
max_power_ma => max_power_ma, power_capped => power_capped,
n_superspeed => n_superspeed, n_fallback => n_fallback,
n_capped => n_capped, n_unconfigured => n_unconfigured);
stim : process
variable seed1 : positive := 7717;
variable seed2 : positive := 2293;
variable r1 : real;
-- VHDL-2008 requires a shared variable to have a protected type, so the
-- bookkeeping lives inside the single stimulus process instead.
variable errors : integer := 0;
variable n_op : cnt5_t := (others => 0);
variable n_exh : integer := 0;
variable n_ss, n_fb, n_cap, n_selfcap, n_claim_only : integer := 0;
variable m_ss, m_fb, m_cap, m_unc : integer := 0;
procedure check(cond : boolean; msg : string) is
begin
if not cond then
errors := errors + 1;
if errors <= 25 then
report " FAIL: " & msg
& " (dss=" & std_logic'image(dss)(2)
& " bos=" & std_logic'image(bos)(2)
& " pss=" & std_logic'image(pss)(2)
& " slu=" & std_logic'image(slu)(2)
& " u2s=" & integer'image(to_integer(unsigned(u2s)))
& " ulr=" & integer'image(to_integer(unsigned(ulr)))
& " sp=" & std_logic'image(sp)(2)
& " | op=" & integer'image(to_integer(unsigned(op_mode)))
& " ssop=" & std_logic'image(ss_operating)(2)
& " grant=" & integer'image(to_integer(unsigned(unit_loads_grant)))
& " ul=" & integer'image(to_integer(unsigned(unit_load_ma)))
& " pwr=" & integer'image(to_integer(unsigned(max_power_ma)))
& ")" severity note;
end if;
end if;
end procedure;
procedure rnd(variable v : out integer; m : integer) is
begin
uniform(seed1, seed2, r1);
v := integer(floor(r1 * real(m)));
end procedure;
procedure check_comb is
variable e_rep, e_op, e_cap : boolean;
variable e_mode : op_mode_t;
variable e_ul, e_ceil, e_grant, e_pwr, req : integer;
begin
-- The reference model is deliberately built the other way round: it
-- computes the ceiling with an explicit min over both limits rather
-- than the design's conditional narrowing.
e_rep := (dss = '1') and (bos = '1');
e_op := (dss = '1') and (pss = '1') and (slu = '1');
case speed_decode(u2s) is
when SP_HIGH => e_mode := OP_HIGH;
when SP_FULL => e_mode := OP_FULL;
when SP_LOW => e_mode := OP_LOW;
when SP_NONE => e_mode := OP_NONE;
end case;
if e_op then
e_mode := OP_SUPER;
end if;
if e_op then e_ul := UL_MA_SS; else e_ul := UL_MA_USB2; end if;
if e_op then e_ceil := UL_CEIL_SS; else e_ceil := UL_CEIL_USB2; end if;
if (sp = '1') and UL_CEIL_SELF < e_ceil then
e_ceil := UL_CEIL_SELF;
end if;
req := to_integer(unsigned(ulr));
if req > e_ceil then
e_grant := e_ceil; e_cap := true;
else
e_grant := req; e_cap := false;
end if;
e_pwr := e_grant * e_ul;
check((ss_capable_reported = '1') = e_rep,
"ss_capable_reported is the DESCRIPTOR claim");
check((ss_operating = '1') = e_op,
"ss_operating needs device AND port AND a trained link");
check(op_mode = mode_code(e_mode), "op_mode matches the model");
check(unit_load_ma = std_logic_vector(to_unsigned(e_ul, 8)),
"unit load is 150 on SS, 100 on USB 2");
check(unit_loads_grant = std_logic_vector(to_unsigned(e_grant, 3)),
"unit loads granted match");
check((power_capped = '1') = e_cap, "power_capped matches the model");
check(max_power_ma = std_logic_vector(to_unsigned(e_pwr, 10)),
"max_power_ma matches the model");
-- Every mechanism follows ss_operating, none follows the claim.
check((credit_flow = '1') = e_op, "credit flow follows ss_operating");
check((async_notify = '1') = e_op, "ERDY/NRDY follows ss_operating");
check((burst_allowed = '1') = e_op, "bursting follows ss_operating");
check((streams_allowed = '1') = e_op, "streams follow ss_operating");
check((link_pm_u1u2 = '1') = e_op, "U1/U2 follows ss_operating");
-- ---- SAFETY PROPERTIES, independent of the model ----
-- 1. THE property. Not one mechanism may be enabled by the claim alone.
if ss_capable_reported = '1' and ss_operating = '0' then
check(credit_flow = '0' and async_notify = '0' and burst_allowed = '0'
and streams_allowed = '0' and link_pm_u1u2 = '0',
"a SuperSpeed mechanism was enabled on a USB 2 link");
check(unit_load_ma = std_logic_vector(to_unsigned(100, 8)),
"a 150 mA unit load was granted on a USB 2 link");
check(op_mode /= mode_code(OP_SUPER),
"op_mode says SuperSpeed but the link never trained");
end if;
-- 2. SuperSpeed operation is impossible without all three terms.
check(ss_operating = '0'
or (dss = '1' and pss = '1' and slu = '1'),
"ss_operating asserted with a term missing");
-- 3. A self-powered device never draws more than one unit load.
if sp = '1' then
check(to_integer(unsigned(unit_loads_grant)) <= 1,
"a self-powered device was granted more than one unit load");
end if;
-- 4. The grant never exceeds the request -- a cap only ever reduces.
check(to_integer(unsigned(unit_loads_grant))
<= to_integer(unsigned(ulr)),
"more unit loads were granted than were requested");
-- 5. The USB 2 ceiling is 5 unit loads (500 mA), never 6.
if ss_operating = '0' then
check(to_integer(unsigned(unit_loads_grant)) <= 5,
"more than 500 mA granted on a USB 2 link");
end if;
-- 6. Power is exactly grant x unit load -- no rounding, no slack.
check(to_integer(unsigned(max_power_ma))
= to_integer(unsigned(unit_loads_grant))
* to_integer(unsigned(unit_load_ma)),
"max_power_ma is not grant x unit load");
-- 7. An unconfigured port grants nothing and runs nothing.
if op_mode = mode_code(OP_NONE) then
check(credit_flow = '0' and burst_allowed = '0'
and streams_allowed = '0',
"mechanisms enabled on a port with no link at all");
end if;
n_op(op_mode_t'pos(e_mode)) := n_op(op_mode_t'pos(e_mode)) + 1;
if e_op then n_ss := n_ss + 1; end if;
if e_rep and not e_op then n_fb := n_fb + 1; end if;
if e_cap then n_cap := n_cap + 1; end if;
if sp = '1' and e_cap then n_selfcap := n_selfcap + 1; end if;
if e_rep and not e_op and speed_decode(u2s) /= SP_NONE then
n_claim_only := n_claim_only + 1;
end if;
end procedure;
procedure step is
begin
wait for 1 ns;
check_comb;
if ss_operating = '1' then m_ss := m_ss + 1; end if;
if ss_capable_reported = '1' and ss_operating = '0' then
m_fb := m_fb + 1;
end if;
if power_capped = '1' then m_cap := m_cap + 1; end if;
if op_mode = mode_code(OP_NONE) then m_unc := m_unc + 1; end if;
wait until rising_edge(clk);
wait for 1 ns;
check(n_superspeed = std_logic_vector(to_unsigned(m_ss, 32)),
"n_superspeed matches the model");
check(n_fallback = std_logic_vector(to_unsigned(m_fb, 32)),
"n_fallback matches the model");
check(n_capped = std_logic_vector(to_unsigned(m_cap, 32)),
"n_capped matches the model");
check(n_unconfigured = std_logic_vector(to_unsigned(m_unc, 32)),
"n_unconfigured matches the model");
end procedure;
procedure hard_reset is
begin
rst_n <= '0'; dss <= '0'; bos <= '0'; sp <= '0'; pss <= '0';
slu <= '0'; ulr <= (others => '0'); u2s <= (others => '0');
wait until rising_edge(clk); wait for 1 ns;
wait until rising_edge(clk); wait for 1 ns;
rst_n <= '1'; wait for 1 ns;
m_ss := 0; m_fb := 0; m_cap := 0; m_unc := 0;
end procedure;
variable iv : integer;
begin
hard_reset;
check(op_mode = mode_code(OP_NONE), "nothing connected, no operating mode");
check(ss_operating = '0', "and nothing is running at SuperSpeed");
-- ===== A. EXHAUSTIVE over the whole resolution =====
-- dev_ss_capable x bos_present x port_ss_capable x ss_link_up
-- x usb2_speed(4) x unit_loads_req(8) x self_powered
-- = 2 x 2 x 2 x 2 x 4 x 8 x 2 = 1024 points, the complete input space.
for a in 0 to 1 loop
for b in 0 to 1 loop
for c in 0 to 1 loop
for d in 0 to 1 loop
for e in 0 to 3 loop
for f in 0 to 7 loop
for g in 0 to 1 loop
if a = 1 then dss <= '1'; else dss <= '0'; end if;
if b = 1 then bos <= '1'; else bos <= '0'; end if;
if c = 1 then pss <= '1'; else pss <= '0'; end if;
if d = 1 then slu <= '1'; else slu <= '0'; end if;
u2s <= std_logic_vector(to_unsigned(e, 2));
ulr <= std_logic_vector(to_unsigned(f, 3));
if g = 1 then sp <= '1'; else sp <= '0'; end if;
step;
n_exh := n_exh + 1;
end loop;
end loop;
end loop;
end loop;
end loop;
end loop;
end loop;
report " exhaustive capability-resolution sweep: " & integer'image(n_exh)
& " of 1024 points verified" severity note;
-- ===== B. directed: the scenarios that get argued about =====
hard_reset;
-- 1. A SuperSpeed device in a SuperSpeed port. Everything on.
dss <= '1'; bos <= '1'; pss <= '1'; slu <= '1'; u2s <= "11";
ulr <= "110"; sp <= '0'; step;
check(op_mode = mode_code(OP_SUPER),
"a trained SS link operates at SuperSpeed");
check(credit_flow = '1' and async_notify = '1' and burst_allowed = '1'
and streams_allowed = '1',
"and every SuperSpeed mechanism is available");
check(to_integer(unsigned(unit_load_ma)) = 150, "the unit load is 150 mA");
check(to_integer(unsigned(unit_loads_grant)) = 6,
"6 unit loads are allowed");
check(to_integer(unsigned(max_power_ma)) = 900, "which is 900 mA");
check(power_capped = '0', "and nothing was capped");
-- 2. THE case. The SAME device, behind a USB 2 hub. It is a USB 2 device.
dss <= '1'; bos <= '1'; pss <= '0'; slu <= '0'; u2s <= "11";
ulr <= "110"; sp <= '0'; step;
check(ss_capable_reported = '1',
"the descriptors still claim SuperSpeed capability");
check(ss_operating = '0', "but nothing SuperSpeed is running");
check(op_mode = mode_code(OP_HIGH), "it is a HIGH SPEED device now");
check(credit_flow = '0', "the host must POLL it");
check(async_notify = '0', "it answers NAK, not NRDY/ERDY");
check(burst_allowed = '0' and streams_allowed = '0',
"no bursting, no streams");
check(link_pm_u1u2 = '0',
"and idle means bus suspend, not U1/U2");
check(to_integer(unsigned(unit_load_ma)) = 100,
"its unit load is 100 mA");
check(to_integer(unsigned(unit_loads_grant)) = 5,
"capped at 5 unit loads");
check(to_integer(unsigned(max_power_ma)) = 500, "so 500 mA, not 900");
check(power_capped = '1', "and the request WAS capped");
-- 3. A SuperSpeed port, a capable device, but training FAILED.
-- Identical outcome to case 2 -- which is the point.
dss <= '1'; bos <= '1'; pss <= '1'; slu <= '0'; u2s <= "11";
ulr <= "110"; sp <= '0'; step;
check(ss_operating = '0',
"a capable device in a capable port still is not SuperSpeed if training failed");
check(op_mode = mode_code(OP_HIGH), "it fell back to high speed");
check(to_integer(unsigned(unit_load_ma)) = 100,
"with a USB 2 unit load");
-- 4. A device that claims SuperSpeed with NO BOS descriptor.
dss <= '1'; bos <= '0'; pss <= '1'; slu <= '1'; u2s <= "11";
ulr <= "010"; sp <= '0'; step;
check(ss_capable_reported = '0',
"with no BOS descriptor there is no reported capability");
check(ss_operating = '1',
"yet the link trained, so SuperSpeed IS operating -- the link decides");
-- 5. A self-powered SuperSpeed device asking for 6 unit loads.
dss <= '1'; bos <= '1'; pss <= '1'; slu <= '1'; u2s <= "11";
ulr <= "110"; sp <= '1'; step;
check(to_integer(unsigned(unit_loads_grant)) = 1,
"a self-powered device gets ONE unit load whatever it asks for");
check(to_integer(unsigned(max_power_ma)) = 150,
"which is 150 mA on SuperSpeed");
check(power_capped = '1', "and the request was capped");
-- 6. A low-speed device: no SuperSpeed anything, 100 mA unit load.
dss <= '0'; bos <= '0'; pss <= '1'; slu <= '0'; u2s <= "01";
ulr <= "001"; sp <= '0'; step;
check(op_mode = mode_code(OP_LOW),
"a low speed device operates at low speed");
check(to_integer(unsigned(unit_load_ma)) = 100,
"with a 100 mA unit load");
check(to_integer(unsigned(max_power_ma)) = 100, "and 100 mA total");
-- 7. Nothing plugged in at all.
dss <= '1'; bos <= '1'; pss <= '1'; slu <= '0'; u2s <= "00";
ulr <= "100"; sp <= '0'; step;
check(op_mode = mode_code(OP_NONE), "no link of either kind means no mode");
check(credit_flow = '0' and burst_allowed = '0',
"and no mechanisms at all");
-- ===== C. randomised =====
-- ieee.math_real.uniform is a genuinely different generator from either
-- Verilog builtin, which is what makes this column independent evidence.
for i in 0 to 39999 loop
rnd(iv, 2); if iv = 1 then dss <= '1'; else dss <= '0'; end if;
rnd(iv, 2); if iv = 1 then bos <= '1'; else bos <= '0'; end if;
rnd(iv, 2); if iv = 1 then pss <= '1'; else pss <= '0'; end if;
rnd(iv, 2); if iv = 1 then slu <= '1'; else slu <= '0'; end if;
rnd(iv, 4); u2s <= std_logic_vector(to_unsigned(iv, 2));
rnd(iv, 8); ulr <= std_logic_vector(to_unsigned(iv, 3));
rnd(iv, 4); if iv = 0 then sp <= '1'; else sp <= '0'; end if;
step;
end loop;
for i in 0 to 4 loop
check(n_op(i) > 0, "every operating mode was reached");
end loop;
check(n_ss > 1000, "SuperSpeed operation was reached often");
check(n_fb > 1000,
"the SS-capable-but-not-operating case was reached often");
check(n_selfcap > 100, "self-powered devices were capped");
check(n_claim_only > 1000,
"the claim-without-operation case was reached with a live USB 2 link");
report " REACH: exhaustive=" & integer'image(n_exh)
& " | modes: none=" & integer'image(n_op(0))
& " low=" & integer'image(n_op(1))
& " full=" & integer'image(n_op(2))
& " high=" & integer'image(n_op(3))
& " super=" & integer'image(n_op(4)) severity note;
report " CASES: ss-operating=" & integer'image(n_ss)
& " fallback=" & integer'image(n_fb)
& " capped=" & integer'image(n_cap)
& " self-capped=" & integer'image(n_selfcap)
& " claim-only-live=" & integer'image(n_claim_only) severity note;
report " COUNTERS: superspeed="
& integer'image(to_integer(unsigned(n_superspeed)))
& " fallback=" & integer'image(to_integer(unsigned(n_fallback)))
& " capped=" & integer'image(to_integer(unsigned(n_capped)))
& " unconfigured="
& integer'image(to_integer(unsigned(n_unconfigured))) severity note;
report " [VHDL] usb3_capability_resolve: " & integer'image(errors)
& " errors" severity note;
if errors = 0 then
report " [VHDL] PASS" severity note;
else
report " [VHDL] FAIL" severity failure;
end if;
running <= false;
wait;
end process;
end architecture;10. Exhaustive Verification
| Measure | Verilog | SystemVerilog | VHDL |
|---|---|---|---|
| Exhaustive points | 1024 / 1024 | 1024 / 1024 | 1024 / 1024 |
OP_NONE reached | 8876 | 9135 | 9006 |
OP_LOW reached | 9009 | 9046 | 9014 |
OP_FULL reached | 9010 | 8979 | 8903 |
OP_HIGH reached | 9005 | 8893 | 8957 |
OP_SUPER reached | 5131 | 4978 | 5151 |
| SuperSpeed operating | 5131 | 4978 | 5151 |
| Capable but NOT operating | 7684 | 7738 | 7613 |
| Power request capped | 15198 | 15033 | 14895 |
| Self-powered and capped | 8013 | 7900 | 7786 |
| Claim-only with a live USB 2 link | 5773 | 5784 | 5706 |
| Result | PASS | PASS | PASS |
All five operating modes were reached in all three languages, and the testbenches assert that rather than merely reporting it.
The row that matters is "capable but NOT operating": about 7700 hits in each run. That is the state the whole module is about, and a suite that reached it a handful of times would not be evidence of anything. It is reached often because the input space makes it common — dev_ss_capable && bos_present is true in a quarter of the space while dev_ss_capable && port_ss_capable && ss_link_up is true in an eighth, so the gap between claim and reality is structurally the common case rather than the exotic one.
11. Mutation Testing
Seven single-change mutants per implementation, suites re-run, failure counts below.
| # | Mutation | Verilog | SysVer | VHDL |
|---|---|---|---|---|
| M1 | ss_operating decided by the descriptor, not the link | 101299 | 102157 | 100901 |
| M2 | Only streams_allowed follows the claim | 19825 | 20012 | 19711 |
| M3 | The 150 mA unit load follows the claim | 26906 | 27104 | 26738 |
| M4 | The self-powered 1-unit-load cap is dropped | 29572 | 29211 | 28641 |
| M5 | The bus-powered ceiling is 6 on both buses | 23455 | 23428 | 23184 |
| M6 | ERDY assumed available at high speed too | 10917 | 10852 | 10902 |
| M7 | A capable device that fell back reports HIGH | 11495 | 11451 | 11467 |
| — | unmutated baseline | 0 | 0 | 0 |
Every mutant dies in every language, the three columns agree to within 3%, and all seven counts are distinct.
M1 is the mutation this chapter exists for, and it produces over 100 000 failures — more than two failures per simulated cycle on average. That magnitude is the point. ss_operating is the single gate on six mechanisms, two power figures and the operating mode, so one wrong line corrupts nine observables simultaneously. In silicon that is not nine bugs, it is one bug wearing nine costumes, and every one of them points somewhere other than the resolver.
M2 is the same error scoped down to one signal, and it is the realistic one. Nobody writes M1; plenty of drivers enable streams from the BOS descriptor because the descriptor is what the stream-capability field lives in. It still dies about 19 800 times, which is worth contrasting with M1: scoping a bug down by a factor of six reduced its detectability by only a factor of five, because the suite checks each mechanism separately rather than checking a summary.
M4 and M5 are the two power mutations, and they die differently. M4 (dropping the self-powered cap) is worth ~29 000 because a quarter of the randomised space is self-powered and the cap almost always binds. M5 (a uniform ceiling of 6) is worth ~23 000 because it only shows up when the request exceeds 5, which is 2 of the 8 request values. Both are distinct from M3 (~26 900), which changes the unit load rather than the count — and the three together confirm the power path is checked as grant, as unit_load, and as their product, not just as the product.
12. UVM: Proving the Claim Never Leaks Into a Decision
12.1 The transaction
class usb3_cap_item extends uvm_sequence_item;
`uvm_object_utils(usb3_cap_item)
rand bit dev_ss_capable;
rand bit bos_present;
rand bit port_ss_capable;
rand bit ss_link_up;
rand usb2_speed_e usb2_speed;
rand bit [2:0] unit_loads_req;
rand bit self_powered;
// A link cannot be up unless both ends offer a pair. This is TRUE of real
// hardware -- and it is a soft constraint on purpose, because a resolver
// must not fall over when a stale register or a forced compliance mode
// asserts ss_link_up without a partner. The adversarial sequence below
// turns it off.
constraint c_link_implies_capable {
soft ss_link_up -> (dev_ss_capable && port_ss_capable);
}
// Most devices publish a BOS descriptor if they are SuperSpeed-capable.
constraint c_bos_usually_matches {
soft bos_present == dev_ss_capable;
}
// Self-powered devices rarely ask for much from the bus.
constraint c_self_powered_modest {
soft self_powered -> unit_loads_req <= 3'd2;
}
function new(string name = "usb3_cap_item"); super.new(name); endfunction
function string convert2string();
return $sformatf(
"dev=%0b bos=%0b port=%0b up=%0b spd=%s ul=%0d self=%0b",
dev_ss_capable, bos_present, port_ss_capable, ss_link_up,
usb2_speed.name(), unit_loads_req, self_powered);
endfunction
endclass12.2 The sequence that finds the bug
// THE sequence for this chapter. Every item is a device that CLAIMS
// SuperSpeed and is NOT operating at SuperSpeed -- the exact population in
// which a claim-driven resolver misbehaves, and a population that ordinary
// random traffic visits only incidentally.
class capable_but_fallen_back_seq extends uvm_sequence #(usb3_cap_item);
`uvm_object_utils(capable_but_fallen_back_seq)
function new(string name = "capable_but_fallen_back_seq");
super.new(name);
endfunction
task body();
repeat (1500) begin
usb3_cap_item it = usb3_cap_item::type_id::create("it");
start_item(it);
if (!it.randomize() with {
dev_ss_capable == 1; // it is capable
bos_present == 1; // and it says so
ss_link_up == 0; // and it is NOT running SuperSpeed
usb2_speed != SP_NONE; // but it IS enumerated on USB 2
unit_loads_req >= 3'd4; // asking for more than USB 2 allows
})
`uvm_error("RAND", "fallen-back randomize failed")
finish_item(it);
end
endtask
endclass
// The adversarial one: ss_link_up asserted with a term missing. A stale
// register, a forced compliance mode, or a port that reports a trained link
// it does not have. The resolver must still refuse SuperSpeed.
class lying_link_seq extends uvm_sequence #(usb3_cap_item);
`uvm_object_utils(lying_link_seq)
function new(string name = "lying_link_seq"); super.new(name); endfunction
task body();
repeat (1000) begin
usb3_cap_item it = usb3_cap_item::type_id::create("it");
start_item(it);
it.c_link_implies_capable.constraint_mode(0);
it.c_bos_usually_matches.constraint_mode(0);
if (!it.randomize() with { ss_link_up == 1;
!(dev_ss_capable && port_ss_capable); })
`uvm_error("RAND", "lying-link randomize failed")
finish_item(it);
end
endtask
endclass
// A self-powered device asking for the maximum, on both buses. Drives the
// interaction of the two ceilings, which is where M4 and M5 live.
class greedy_self_powered_seq extends uvm_sequence #(usb3_cap_item);
`uvm_object_utils(greedy_self_powered_seq)
function new(string name = "greedy_self_powered_seq"); super.new(name); endfunction
task body();
repeat (1000) begin
usb3_cap_item it = usb3_cap_item::type_id::create("it");
start_item(it);
it.c_self_powered_modest.constraint_mode(0);
if (!it.randomize() with { self_powered == 1;
unit_loads_req >= 3'd5; })
`uvm_error("RAND", "greedy randomize failed")
finish_item(it);
end
endtask
endclass12.3 The scoreboard
class usb3_cap_scoreboard extends uvm_scoreboard;
`uvm_component_utils(usb3_cap_scoreboard)
uvm_analysis_imp #(usb3_cap_mon_item, usb3_cap_scoreboard) ap;
int unsigned n_super, n_fallback, n_lying, n_self_capped;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
function void write(usb3_cap_mon_item t);
// ---- THE check. The claim must not have leaked into any decision. ----
if (t.ss_capable_reported && !t.ss_operating) begin
if (t.credit_flow || t.async_notify || t.burst_allowed
|| t.streams_allowed || t.link_pm_u1u2)
`uvm_error("CLAIM_LEAK", $sformatf(
"a SuperSpeed mechanism enabled on a USB 2 link: credits=%0b erdy=%0b burst=%0b streams=%0b u1u2=%0b",
t.credit_flow, t.async_notify, t.burst_allowed,
t.streams_allowed, t.link_pm_u1u2))
if (t.unit_load_ma != 8'd100)
`uvm_error("CLAIM_LEAK", $sformatf(
"a %0d mA unit load granted on a USB 2 link", t.unit_load_ma))
if (t.op_mode == OP_SUPER)
`uvm_error("CLAIM_LEAK",
"op_mode says SuperSpeed but no SuperSpeed link trained")
n_fallback++;
end
// ---- ss_operating requires all three terms, whatever the link claims --
if (t.ss_operating) begin
if (!(t.dev_ss_capable && t.port_ss_capable && t.ss_link_up))
`uvm_error("TERM_MISSING",
"ss_operating asserted with a term missing -- a lying link was believed")
n_super++;
end
if (t.ss_link_up && !(t.dev_ss_capable && t.port_ss_capable))
n_lying++;
// ---- Power: both ceilings, and the grant never exceeds the request ----
if (t.self_powered && t.unit_loads_grant > 3'd1)
`uvm_error("POWER",
$sformatf("a self-powered device was granted %0d unit loads",
t.unit_loads_grant))
if (!t.ss_operating && t.unit_loads_grant > 3'd5)
`uvm_error("POWER", "more than 500 mA granted on a USB 2 link")
if (t.unit_loads_grant > t.unit_loads_req)
`uvm_error("POWER", "more unit loads granted than requested")
if (t.max_power_ma != t.unit_loads_grant * t.unit_load_ma)
`uvm_error("POWER", "max_power_ma is not grant x unit load")
if (t.self_powered && t.power_capped) n_self_capped++;
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("SB", $sformatf(
"superspeed=%0d fallback=%0d lying-links=%0d self-capped=%0d",
n_super, n_fallback, n_lying, n_self_capped), UVM_LOW)
// A run that never reached the interesting population proves nothing.
if (n_super == 0) `uvm_error("COVERAGE", "SuperSpeed never operated")
if (n_fallback == 0) `uvm_error("COVERAGE",
"the capable-but-fallen-back population was never reached -- the central case is untested")
if (n_lying == 0) `uvm_error("COVERAGE", "no lying link was ever presented")
if (n_self_capped == 0) `uvm_error("COVERAGE", "the self-powered cap never bound")
endfunction
endclass12.4 Functional coverage
covergroup cap_resolve_cg with function sample(
bit dev, bit bos, bit port_c, bit up, usb2_speed_e spd,
bit [2:0] ul, bit self_p, op_mode_e mode, bit oper);
cp_mode : coverpoint mode {
bins all[] = {OP_NONE, OP_LOW, OP_FULL, OP_HIGH, OP_SUPER};
}
cp_claim : coverpoint (dev && bos) { bins yes = {1}; bins no = {0}; }
cp_oper : coverpoint oper { bins yes = {1}; bins no = {0}; }
cp_spd : coverpoint spd { bins all[] = {SP_NONE, SP_LOW,
SP_FULL, SP_HIGH}; }
cp_ul : coverpoint ul { bins req[8] = {[0:7]}; }
cp_self : coverpoint self_p { bins yes = {1}; bins no = {0}; }
// THE cross. The bin that matters is (claim=1, oper=0): a device that says
// SuperSpeed and is not running it. Closing this cross is the coverage
// statement that the central case was actually exercised.
x_claim_vs_oper : cross cp_claim, cp_oper;
// Every operating mode under every request size -- where the two power
// ceilings interact with the bus choice.
x_mode_ul : cross cp_mode, cp_ul;
// The self-powered cap on both buses.
x_self_oper : cross cp_self, cp_oper, cp_ul;
// A link claiming to be up without both ends capable: the adversarial bin.
cp_lying : coverpoint (up && !(dev && port_c)) {
bins seen = {1};
}
endgroupx_claim_vs_oper has four bins and only one of them is interesting, which is exactly why it should be a coverage goal rather than an assumption. A regression that closes the other three and leaves (claim=1, oper=0) open has tested everything except the thing that breaks.
13. SystemVerilog Assertions
module usb3_capability_resolve_sva
import usb3_cap_pkg::*;
(
input logic clk,
input logic rst_n,
input logic dev_ss_capable,
input logic bos_present,
input logic [2:0] unit_loads_req,
input logic self_powered,
input logic port_ss_capable,
input logic ss_link_up,
input usb2_speed_e usb2_speed,
input logic ss_capable_reported,
input logic ss_operating,
input op_mode_e op_mode,
input logic credit_flow,
input logic async_notify,
input logic burst_allowed,
input logic streams_allowed,
input logic link_pm_u1u2,
input logic [7:0] unit_load_ma,
input logic [2:0] unit_loads_grant,
input logic [9:0] max_power_ma,
input logic power_capped
);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
// ---- 1. THE property. All three terms, or no SuperSpeed. ----
property p_operating_needs_all_three;
ss_operating |-> (dev_ss_capable && port_ss_capable && ss_link_up);
endproperty
a_operating_needs_all_three : assert property (p_operating_needs_all_three)
else $error("ss_operating asserted with a term missing");
// ---- 2. The contrapositive, stated over every mechanism at once. ----
property p_no_mechanism_without_link;
!ss_operating |-> (!credit_flow && !async_notify && !burst_allowed
&& !streams_allowed && !link_pm_u1u2);
endproperty
a_no_mechanism_without_link : assert property (p_no_mechanism_without_link)
else $error("a SuperSpeed mechanism is enabled with no SuperSpeed link");
// ---- 3. The claim gates nothing. Stated as an independence property. ----
property p_claim_gates_nothing;
(ss_capable_reported && !ss_operating) |-> (unit_load_ma == 8'd100);
endproperty
a_claim_gates_nothing : assert property (p_claim_gates_nothing)
else $error("the descriptor claim leaked into the power decision");
// ---- 4. OP_SUPER is reachable only through a trained link. ----
property p_super_needs_link;
(op_mode == OP_SUPER) |-> ss_operating;
endproperty
a_super_needs_link : assert property (p_super_needs_link);
// ---- 5. A self-powered device never exceeds one unit load. ----
property p_self_powered_one_load;
self_powered |-> (unit_loads_grant <= 3'd1);
endproperty
a_self_powered_one_load : assert property (p_self_powered_one_load)
else $error("a self-powered device was granted %0d unit loads",
unit_loads_grant);
// ---- 6. The USB 2 ceiling is 5 unit loads. ----
property p_usb2_ceiling;
!ss_operating |-> (unit_loads_grant <= 3'd5);
endproperty
a_usb2_ceiling : assert property (p_usb2_ceiling);
// ---- 7. A cap only ever reduces. ----
property p_grant_never_exceeds_request;
unit_loads_grant <= unit_loads_req;
endproperty
a_grant_never_exceeds_request :
assert property (p_grant_never_exceeds_request);
// ---- 8. power_capped means exactly "the request did not fit". ----
property p_capped_iff_reduced;
power_capped == (unit_loads_grant != unit_loads_req);
endproperty
a_capped_iff_reduced : assert property (p_capped_iff_reduced)
else $error("power_capped disagrees with the grant it describes");
// ---- 9. The power figure is the product, with no slack. ----
property p_power_is_product;
max_power_ma == (10'(unit_loads_grant) * 10'(unit_load_ma));
endproperty
a_power_is_product : assert property (p_power_is_product);
// ---- Cover: the central population was reached. ----
c_fallback : cover property ((ss_capable_reported && !ss_operating));
c_super : cover property ((ss_operating));
c_selfcap : cover property ((self_powered && power_capped));
c_lying : cover property ((ss_link_up
&& !(dev_ss_capable && port_ss_capable)));
endmodule
bind usb3_capability_resolve usb3_capability_resolve_sva u_sva (.*);14. Debugging Walkthrough: "It Works on My Machine, Slowly"
The report. A USB 3 storage device delivers about 30 MB/s on one laptop and about 400 MB/s on another. Both are USB 3 laptops. The device is the same. The cable is the same.
Step 1 — is it running SuperSpeed at all? 30 MB/s is suspiciously close to USB 2 high-speed's practical ceiling. Read ss_operating, not the descriptors. On the slow machine it is low. The device is enumerating on the USB 2 bus.
Step 2 — which of the three terms failed? This is why the resolver takes three separate inputs rather than one pre-combined "SuperSpeed available" bit. Read them individually:
| fast laptop | slow laptop | |
|---|---|---|
dev_ss_capable | 1 | 1 |
port_ss_capable | 1 | 1 |
ss_link_up | 1 | 0 |
The device is capable. The port is capable. Training failed. So this is not a configuration problem and not a descriptor problem — it is Chapter 20.3's problem, and the investigation moves to the LTSSM.
Step 3 — how far did training get? Read the LTSSM's poll_timeouts and ever_compliance from Chapter 20.3. Non-zero: Polling timed out, and the state machine parked in Compliance. The far end never answered the LFPS handshake.
Step 4 — the physical cause. Polling times out when the receiver cannot lock. On the slow laptop the front-panel port is on a long internal cable to the motherboard header — marginal at 5 Gb/s, fine at 480 Mb/s. The rear ports work.
Step 5 — what the product should have done. Nothing here required a bug report. n_fallback had been incrementing on every enumeration, and a single log line — "device claims SuperSpeed but is operating at high speed; training failed" — would have pointed a user at a different port in one step. The counter costs 32 flops and the message costs a string.
15. Common Misconceptions
"A USB 3 device is a USB 3 device." Only if a SuperSpeed link trained. Behind a USB 2 hub, through a charge-only cable, or after a training failure, it is a USB 2 device and every USB 2 rule applies. This is the whole chapter.
"ss_link_up is enough on its own." In a correct system it implies the other two. But it arrives from a state machine that can be forced into Compliance mode and from registers that survive re-enumeration, so it can be asserted without a real partner. The lying_link_seq above exists for precisely this, and requiring all three terms is what survives it.
"The BOS descriptor tells you what to enable." It tells you what the device could do. What to enable is decided by the link. A driver that enables streams from the descriptor produces an endpoint that never transfers — mutation M2, ~19 800 failures.
"Falling back to USB 2 is a graceful degradation, so it is fine." It is graceful and it is a twenty-fold performance loss, delivered silently. Graceful degradation that nobody is told about is indistinguishable from a slow product.
"USB 3 doubles the power because the unit load went from 100 to 150 mA." The unit load went up by half, and the count went from 5 to 6, so the bus-powered maximum went from 500 mA to 900 mA — a factor of 1.8, from two independent changes. And neither applies to a self-powered device, which draws one unit load on either bus.
"Both CRCs, credits, ERDY, U1 — these are just USB 2 features done better." They are features of a layer USB 2 does not have. There is no USB 2 link layer, so there is no USB 2 version of any of them to improve on. That is why USB 3 needed a new stack rather than a new revision.
"If ss_capable_reported and ss_operating are usually the same, one of them is redundant." They differ in roughly 7700 of 41 000 sampled cycles — about 19% — and that 19% is where every bug in this chapter lives.
16. Exercises
1. Change ss_operating to dev_ss_capable && ss_link_up — dropping only port_ss_capable. Predict whether this dies, and roughly how hard, before running it. Then explain why the exhaustive sweep catches it even though no physically realisable system distinguishes the two expressions.
2. Add a usb3_gen2 input (10 Gb/s) and a fifth operating mode. Which of the nine SVA properties need changing, and which are already correct as written? The answer should be "one" — find it and say why the other eight are insensitive.
3. The design applies the self-powered cap after the mode ceiling. Rewrite it to take the minimum of all three limits in one expression, confirm the 1024-point sweep still passes, then re-measure M4 and M5. Did either become harder to kill? If so, that is an argument about readability with a number attached.
4. Write the mutation that makes power_capped assert whenever unit_loads_req is 6 or 7, regardless of the mode. Show which SVA property catches it (hint: property 8's equivalence, not its implication half) and construct the input that distinguishes it from correct behaviour.
5. The Icarus RNG finding in section 9.2 means the Verilog and SystemVerilog columns of every mutation table in this module were the same stimulus wherever both testbenches drew the same number of values per iteration. Look at Chapter 20.1's matrix, note which rows match exactly, and work out from the testbench code why Chapter 20.4's rows do not.
6. Instrument n_fallback into a real system: emit one log line the first time it increments per enumeration, and not again. Write the property that the message is emitted at most once per connect, and the mutation that makes it emit on every packet.
17. Summary
| Idea | Why it matters |
|---|---|
| USB 3 added a bus, it did not replace one | a device may end up operating on either |
| SuperSpeed has a link layer; USB 2 has none | header CRCs, LBAD, credits, U1/U2 have no USB 2 analogue |
| Polling vs credits | changes which side initiates traffic |
| NAK vs NRDY/ERDY | changes which side is left holding the work |
| Unit load 100 mA vs 150 mA | and 5 vs 6 loads: 500 mA vs 900 mA |
| Self-powered: 1 unit load on either bus | the one power rule the two buses share |
ss_capable_reported — the claim | good for logging. Gates nothing. |
ss_operating — device AND port AND trained link | gates every mechanism and both power figures |
| A capable device behind a USB 2 hub is a USB 2 device | the central fact of the chapter |
n_fallback | a silent graceful fallback is a support incident |
| 1024-point exhaustive verification | the complete input space, all five modes asserted reached |
| 7 mutations, all killed in 3 languages | M1 at over 100 000 failures: one gate, nine observables |
Tooling
| Step | Command |
|---|---|
| Verilog-2005 | iverilog -g2005 -o cr_v.out cr_v.v cr_v_tb.v && ./cr_v.out |
| SystemVerilog | iverilog -g2012 -o cr_sv.out cr_sv.sv cr_sv_tb.sv && ./cr_sv.out |
| VHDL-2008 analyse | nvc --std=2008 -a cr_vhdl.vhd cr_vhdl_tb.vhd |
| VHDL-2008 elaborate | nvc --std=2008 -e tb_cr_vhdl |
| VHDL-2008 run | nvc --std=2008 -r tb_cr_vhdl |
| One mutation | iverilog -g2005 -DMUT_M1 -o mm cr_v_mut.v cr_v_tb.v && ./mm |
All three implementations pass with 0 errors: 1024 of 1024 exhaustive points, 40 000 randomised cycles from three genuinely independent generators, and all five operating modes reached and asserted reached.
18. Module 20 Complete
Five chapters, five synthesisable blocks, fifteen implementations, and thirty-five mutations — all killed in all three languages.
| Chapter | Block | The structural idea |
|---|---|---|
| 20.1 | usb3_credit_flow | a sender that is given permission, not asked for it |
| 20.2 | usb3_dual_bus_arbiter | two buses in one cable; exactly one may drive |
| 20.3 | usb3_ltssm | a link must train itself without using the link |
| 20.4 | usb3_packet_decode | the header is protected separately from the payload |
| 20.5 | usb3_capability_resolve | capability is a property of the link, not the device |
Read as one argument, they say something narrower than "USB 3 is faster." SuperSpeed moved four decisions that USB 2 left to the host — when to send, who may drive, whether the channel is ready, and what a packet is — and put each one somewhere it can be decided locally, with its own state and its own recovery. The speed is a consequence of that, not the cause.
And every one of the five blocks fails in the same shape when it is written carelessly: it acts on information it is not entitled to trust yet. A sender that transmits before it has credits. An arbiter that drives before the bus is resolved. A receiver that reports a trained link before training finished. A decoder that reads a header before its CRC. A resolver that believes a descriptor instead of a link. Five chapters, one discipline.
Continue learning
Related tutorials
- Related topic
SuperSpeed Concepts
USB 3 kept the single master and deleted the polling — credit-based flow control, announced readiness, and a sender bounded by the smallest of three limits.
- Related topic
Hub Power Management
A bus-powered hub gets 500 mA and must supply four ports that could each want 500 mA — so its ports are offered one unit load, and a device needing more is refused.
- 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
Dual-Bus Architecture
A USB 3 cable carries two complete buses — physically parallel, logically exclusive — and the presence pull-up deliberately sits outside that exclusion.
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.
