Skip to content
VLSI Mentor

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

Two protocol stacks side by side. The USB 2 stack has a function layer, a protocol layer, no link layer, and a bus interface driving the D-plus D-minus pair. The SuperSpeed stack has a function layer, a protocol layer, a link layer handling header CRCs and LBAD, and a physical layer driving two differential pairs.Function layerendpoints, descriptorsUSB 2 protocolhost polls, device NAKsno link layernothing sits here on USB 2Bus interfaceone D+ / D- pair, half duplexFunction layerendpoints, BOS descriptorSS protocolcredits, NRDY, ERDY, streamsSS link layerheader CRC, LBAD, U1 / U2SS physicaltwo pairs, full duplex, LFPS12
SuperSpeed inserts a link layer between the protocol layer and the physical layer. USB 2 has no equivalent — which is why header CRCs, LBAD, credits and U1/U2 have no USB 2 counterpart to fall back to.

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 2SuperSpeed
Flow controlhost polls; device answers when askeddevice is given credits and sends when it has them
Device busyNAK — "ask me again" (host keeps polling)NRDY then ERDY — "I will tell you"
Directionhalf duplex, one pair, one direction at a timefull duplex, separate TX and RX pairs
Burstingone packet per tokenup to bMaxBurst packets back to back
Streamsnonemultiple logical streams on one bulk endpoint
Link trainingnone neededmandatory LTSSM (ch 20.3)
Idlebus-wide SUSPENDper-link U1 / U2, plus U3 for suspend
Unit load100 mA150 mA
Bus-powered max5 unit loads = 500 mA6 unit loads = 900 mA
Self-powered max1 unit load1 unit load
Header protectionno separate header CRC14-byte header with its own CRC-16
Enumerationreset, address, descriptorsthe 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   "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:

  1. 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.
  2. 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.
  3. 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

Two horizontal chains. The descriptor chain combines dev_ss_capable and bos_present into ss_capable_reported, which gates nothing. The link chain combines dev_ss_capable, port_ss_capable and ss_link_up into ss_operating, which gates every SuperSpeed mechanism.DESCRIPTOR chaindev_ss_capable AND bos_presentss_capable_reportedwhat the device claimsGates NOTHINGlogging and user hints onlyLINK chaindev_ss_capable ANDport_ss_capable AND ss_link_upss_operatingwhat is actually runningGates EVERYTHINGcredits, ERDY, burst, streams,U1 / U2, 150 mA12
The descriptor chain produces a claim that is good for logging and for telling a user their device would be faster elsewhere. The link chain produces the answer that gates every mechanism. Note that dev_ss_capable appears in both — it is necessary for either, and sufficient for neither.

4. What We Are Building

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 reachable

5. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

Three 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

7. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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 cycles
A ten-cycle waveform. For the first five cycles port_ss_capable and ss_link_up are high, ss_operating is high, op_mode reads SUPER, credit_flow is high, the unit load is 150 milliamps and the power grant is 900 milliamps. From cycle 5 port_ss_capable and ss_link_up go low, ss_capable_reported stays high, ss_operating goes low, op_mode reads HIGH, credit_flow goes low, the unit load becomes 100 milliamps and the grant becomes 500 milliamps.same device, USB 2 hubsame device, USB 2 hubclkport_ss_capabless_link_upss_capable_reportedss_operatingop_modeSUPERSUPERSUPERSUPERSUPERHIGHHIGHHIGHHIGHHIGHcredit_flowunit_load_ma150150150150150100100100100100max_power_ma900900900900900500500500500500t0t1t2t3t4t5t6t7t8t9
Cycles 0–4: a SuperSpeed port, trained. Cycles 5–9: the same device behind a USB 2 hub. ss_capable_reported never changes, because the device never changed. ss_operating, op_mode, credit_flow, the unit load and the power grant all change, because the link did.

ss_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
endmodule

9.2 SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`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
endmodule

9.3 VHDL testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.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

MeasureVerilogSystemVerilogVHDL
Exhaustive points1024 / 10241024 / 10241024 / 1024
OP_NONE reached887691359006
OP_LOW reached900990469014
OP_FULL reached901089798903
OP_HIGH reached900588938957
OP_SUPER reached513149785151
SuperSpeed operating513149785151
Capable but NOT operating768477387613
Power request capped151981503314895
Self-powered and capped801379007786
Claim-only with a live USB 2 link577357845706
ResultPASSPASSPASS

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.

#MutationVerilogSysVerVHDL
M1ss_operating decided by the descriptor, not the link101299102157100901
M2Only streams_allowed follows the claim198252001219711
M3The 150 mA unit load follows the claim269062710426738
M4The self-powered 1-unit-load cap is dropped295722921128641
M5The bus-powered ceiling is 6 on both buses234552342823184
M6ERDY assumed available at high speed too109171085210902
M7A capable device that fell back reports HIGH114951145111467
—unmutated baseline000

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endclass

12.2 The sequence that finds the bug

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endclass

12.3 The scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endclass

12.4 Functional coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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};
  }
endgroup

x_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 laptopslow laptop
dev_ss_capable11
port_ss_capable11
ss_link_up10

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

IdeaWhy it matters
USB 3 added a bus, it did not replace onea device may end up operating on either
SuperSpeed has a link layer; USB 2 has noneheader CRCs, LBAD, credits, U1/U2 have no USB 2 analogue
Polling vs creditschanges which side initiates traffic
NAK vs NRDY/ERDYchanges which side is left holding the work
Unit load 100 mA vs 150 mAand 5 vs 6 loads: 500 mA vs 900 mA
Self-powered: 1 unit load on either busthe one power rule the two buses share
ss_capable_reported — the claimgood for logging. Gates nothing.
ss_operating — device AND port AND trained linkgates every mechanism and both power figures
A capable device behind a USB 2 hub is a USB 2 devicethe central fact of the chapter
n_fallbacka silent graceful fallback is a support incident
1024-point exhaustive verificationthe complete input space, all five modes asserted reached
7 mutations, all killed in 3 languagesM1 at over 100 000 failures: one gate, nine observables

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o cr_v.out cr_v.v cr_v_tb.v && ./cr_v.out
SystemVerilogiverilog -g2012 -o cr_sv.out cr_sv.sv cr_sv_tb.sv && ./cr_sv.out
VHDL-2008 analysenvc --std=2008 -a cr_vhdl.vhd cr_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_cr_vhdl
VHDL-2008 runnvc --std=2008 -r tb_cr_vhdl
One mutationiverilog -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.

ChapterBlockThe structural idea
20.1usb3_credit_flowa sender that is given permission, not asked for it
20.2usb3_dual_bus_arbitertwo buses in one cable; exactly one may drive
20.3usb3_ltssma link must train itself without using the link
20.4usb3_packet_decodethe header is protected separately from the payload
20.5usb3_capability_resolvecapability 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

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.