Skip to content
VLSI Mentor

USB · Module 30

Compliance Review Checklist

Four different questions get called compliance, and confusing them is how a device that works on every desk fails certification. Separating functional correctness from specification conformance, worked on an exhaustively verified Chapter 9 request-legality table.

A device can pass every test its team wrote, work on every machine in the building, and still be out of conformance with the specification it claims to implement. It can also be perfectly conformant and fail certification on an electrical measurement nobody in the RTL team has ever seen.

This chapter is about not confusing those situations with each other.

1. Four Things Called Compliance

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  TUTORIAL / TEAM REVIEW
       Does this design do what we intended?
       Settled by: our own tests, our own review.
       Proves: nothing about the specification.

    2  FUNCTIONAL VERIFICATION
       Does the implementation match its specification document?
       Settled by: simulation against a model derived from the spec.
       Proves: the behaviours we modelled. Nothing about the ones we
       did not think to model, and nothing electrical.

    3  SPECIFICATION CONFORMANCE
       Does the device satisfy every NORMATIVE requirement that applies
       to it -- including the ones no host on our desk exercises?
       Settled by: a systematic reading of the specification against the
       design, plus targeted tests.
       Proves: conformance to our reading of the document.

    4  OFFICIAL USB-IF COMPLIANCE AND CERTIFICATION
       Does the device pass the compliance program -- the published test
       suites, on the published equipment, at an authorised event or
       lab -- and may it use the logo?
       Settled by: that process, and nothing else.
       Proves: exactly what the program says it proves.

2. The Gap Functional Testing Cannot Close

Every one of these is a real and common shape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    WORKS ON ONE HOST, VIOLATES THE SPECIFICATION
      The host never sends the request the device gets wrong. Different
      host stacks send different subsets, and the subset your laptop
      sends is not the specification.

    PASSES PROTOCOL SIMULATION, FAILS ELECTRICAL COMPLIANCE
      Eye diagram, rise and fall times, inrush current, droop. RTL has
      no opinion about any of it, and a functionally perfect design can
      fail on a board layout.

    ENUMERATES, THEN VIOLATES CHAPTER 9 LATER
      Enumeration exercises a handful of requests in a fixed order. The
      requirements that apply AFTER configuration -- what is legal in
      which device state -- are not exercised by plugging it in.

    CONFORMS, BUT NOT INTEROPERABLY
      Conformant behaviour that a popular host stack handles badly.
      Real, and it is a business problem rather than a specification one.

The third is the one an RTL reviewer owns, and it is the subject of the rest of this chapter. The gap is precise: enumeration is a path through the state machine, and conformance is a property of the whole state machine.

3. The Specimen: A Chapter 9 Legality Table

The question this block answers is narrow and it is a conformance question rather than a functional one:

Of the standard requests the specification defines, which is this device obliged to accept in the state it is currently in, and which must it reject?

A device that answers only the requests its host happens to send will enumerate and work. A device that accepts a request it should have rejected is out of conformance, and nothing visibly breaks — until a host that sends it arrives.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    SCOPE       USB 2.0 STANDARD device requests only.
    NOT IN SCOPE class-specific and vendor-specific requests, which are
                defined by their class document rather than by Chapter 9.
                The block reports them as "not a standard request" and
                declines to have an opinion, which is itself a
                conformance decision -- see section 7.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_req_legal -- does the specification REQUIRE the device to handle
//  this standard request in the state it is currently in?
//
//  CLASSIFICATION: simplified synthesisable teaching RTL, and it is a
//  CONFORMANCE decision rather than a functional one. That distinction
//  is the whole reason chapter 30.3 builds it.
//
//  A device that answers only the requests its host happens to send
//  will enumerate, work, and ship. This block answers a different
//  question: of the requests the specification defines, which ones is
//  the device OBLIGED to accept right now, and which must it reject?
//  A device that accepts a request it should have rejected is out of
//  conformance even though nothing visibly breaks -- until a host that
//  sends it arrives.
//
//  SCOPE: USB 2.0 STANDARD device requests only. Class-specific and
//  vendor-specific requests are outside it by construction: they are
//  defined by their class document, not by Chapter 9, and this block
//  reports them as "not a standard request" rather than guessing.
//
//  WHAT THIS BLOCK IS NOT
//    It is not a compliance test. It encodes one table from one chapter
//    of one specification. Passing it proves that this table was
//    implemented; it proves nothing about descriptors, electricals,
//    timing, class behaviour or interoperability. See 30.3 section 3.
// =====================================================================
module usb_req_legal (
  // ---- the device's current state ----
  //  0 DEFAULT     powered and reset, no address assigned
  //  1 ADDRESS     an address has been assigned, not yet configured
  //  2 CONFIGURED  a non-zero configuration has been selected
  input  wire [1:0] dev_state,

  // ---- the SETUP packet, decoded ----
  input  wire       dir_in,       // 1 = device-to-host (a GET)
  input  wire [1:0] recipient,    // 0 DEVICE, 1 INTERFACE, 2 ENDPOINT
  input  wire [1:0] req_type,     // 0 STANDARD, 1 CLASS, 2 VENDOR
  input  wire [7:0] b_request,
  input  wire [15:0] w_value,
  input  wire [15:0] w_index,
  input  wire [15:0] w_length,

  // ---- the device's configuration, for the bounds checks ----
  input  wire [7:0] n_configs,    // how many configurations exist
  input  wire [7:0] n_interfaces, // how many interfaces the current config has
  input  wire [3:0] n_endpoints,  // highest endpoint number implemented

  // ---- the verdict ----
  output wire       must_handle,  // the device is obliged to accept it
  output wire       must_stall,   // the device is obliged to reject it
  output wire [3:0] reason
);

  localparam [7:0] R_GET_STATUS   = 8'd0,
                   R_CLEAR_FEAT   = 8'd1,
                   R_SET_FEAT     = 8'd3,
                   R_SET_ADDRESS  = 8'd5,
                   R_GET_DESC     = 8'd6,
                   R_SET_DESC     = 8'd7,
                   R_GET_CONFIG   = 8'd8,
                   R_SET_CONFIG   = 8'd9,
                   R_GET_IFACE    = 8'd10,
                   R_SET_IFACE    = 8'd11,
                   R_SYNCH_FRAME  = 8'd12;

  localparam [1:0] ST_DEFAULT = 2'd0, ST_ADDRESS = 2'd1, ST_CONFIGURED = 2'd2;
  localparam [1:0] RCP_DEVICE = 2'd0, RCP_IFACE  = 2'd1, RCP_ENDPOINT  = 2'd2;

  // Reason codes. A verdict without a reason is unreviewable and
  // undebuggable: "STALL" on a bus trace tells you nothing about which
  // rule the device thought it was applying.
  localparam [3:0] OK             = 4'd0,
                   E_NOT_STANDARD = 4'd1,  // class or vendor request
                   E_UNKNOWN_REQ  = 4'd2,  // not a defined standard request
                   E_WRONG_STATE  = 4'd3,  // undefined in this device state
                   E_BAD_DIR      = 4'd4,  // data direction contradicts the request
                   E_BAD_RECIP    = 4'd5,  // recipient not defined for this request
                   E_BAD_INDEX    = 4'd6,  // interface or endpoint does not exist
                   E_BAD_VALUE    = 4'd7,  // wValue out of range
                   E_BAD_LENGTH   = 4'd8;  // wLength wrong for this request

  // ---- is this a standard request at all ----
  wire is_standard = (req_type == 2'd0);

  wire known_req =
      (b_request == R_GET_STATUS)  || (b_request == R_CLEAR_FEAT) ||
      (b_request == R_SET_FEAT)    || (b_request == R_SET_ADDRESS) ||
      (b_request == R_GET_DESC)    || (b_request == R_SET_DESC) ||
      (b_request == R_GET_CONFIG)  || (b_request == R_SET_CONFIG) ||
      (b_request == R_GET_IFACE)   || (b_request == R_SET_IFACE) ||
      (b_request == R_SYNCH_FRAME);

  // ---- direction: a GET returns data, a SET does not ----
  wire want_in =
      (b_request == R_GET_STATUS) || (b_request == R_GET_DESC) ||
      (b_request == R_GET_CONFIG) || (b_request == R_GET_IFACE) ||
      (b_request == R_SYNCH_FRAME);
  wire dir_ok = (dir_in == want_in);

  // ---- recipient: which recipients each request is defined for ----
  wire recip_ok =
      (b_request == R_GET_STATUS)  ? (recipient <= RCP_ENDPOINT) :
      (b_request == R_CLEAR_FEAT)  ? (recipient <= RCP_ENDPOINT) :
      (b_request == R_SET_FEAT)    ? (recipient <= RCP_ENDPOINT) :
      (b_request == R_GET_IFACE)   ? (recipient == RCP_IFACE)    :
      (b_request == R_SET_IFACE)   ? (recipient == RCP_IFACE)    :
      (b_request == R_SYNCH_FRAME) ? (recipient == RCP_ENDPOINT) :
                                     (recipient == RCP_DEVICE);

  // ---- state: where each request is DEFINED ----
  //  In the Default state only three requests are defined. Everything
  //  else is undefined behaviour, and "undefined" is not "harmless":
  //  a device that answers GET_CONFIGURATION before it has an address
  //  is reporting a configuration it cannot have.
  wire def_ok =
      (b_request == R_SET_ADDRESS) || (b_request == R_GET_DESC) ||
      (b_request == R_SET_DESC);

  //  In the Address state the interface- and endpoint-directed forms
  //  are defined only for interface zero and endpoint zero, because no
  //  others exist until a configuration is selected.
  wire addr_ok =
      (b_request != R_GET_IFACE) && (b_request != R_SET_IFACE) &&
      (b_request != R_SYNCH_FRAME);

  wire state_ok =
      (dev_state == ST_DEFAULT)    ? def_ok  :
      (dev_state == ST_ADDRESS)    ? addr_ok :
      (dev_state == ST_CONFIGURED) ? 1'b1    : 1'b0;

  // ---- index and value bounds ----
  wire iface_exists =
      (dev_state == ST_CONFIGURED) ? (w_index[7:0] < n_interfaces)
                                   : (w_index[7:0] == 8'd0);
  wire ep_exists =
      (dev_state == ST_CONFIGURED) ? (w_index[3:0] <= n_endpoints)
                                   : (w_index[3:0] == 4'd0);

  wire index_ok =
      (recipient == RCP_IFACE)    ? iface_exists :
      (recipient == RCP_ENDPOINT) ? ep_exists    : 1'b1;

  wire value_ok =
      (b_request == R_SET_ADDRESS) ? (w_value <= 16'd127) :
      (b_request == R_SET_CONFIG)  ? (w_value[7:0] <= n_configs) : 1'b1;

  // ---- length: the no-data-stage requests must ask for no data ----
  wire zero_len_req =
      (b_request == R_CLEAR_FEAT)  || (b_request == R_SET_FEAT) ||
      (b_request == R_SET_ADDRESS) || (b_request == R_SET_CONFIG) ||
      (b_request == R_SET_IFACE);
  wire length_ok = zero_len_req ? (w_length == 16'd0) : 1'b1;

  // ---- the verdict, in the order a reviewer would apply the rules ----
  assign reason =
      !is_standard ? E_NOT_STANDARD :
      !known_req   ? E_UNKNOWN_REQ  :
      !dir_ok      ? E_BAD_DIR      :
      !recip_ok    ? E_BAD_RECIP    :
      !state_ok    ? E_WRONG_STATE  :
      !index_ok    ? E_BAD_INDEX    :
      !value_ok    ? E_BAD_VALUE    :
      !length_ok   ? E_BAD_LENGTH   : OK;

  assign must_handle = (reason == OK);
  // A class or vendor request is NOT this block's to reject. It reports
  // the fact and leaves the decision to whatever implements that class:
  // a device with no class handler stalls it, and a device with one
  // does not, and neither is a Chapter 9 question.
  assign must_stall  = (reason != OK) && (reason != E_NOT_STANDARD);

endmodule

4. Question — Is This Request DEFINED In This State?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          a request is accepted only in the device states
                       where the specification defines it; in every other
                       state the device rejects it
    EVIDENCE           the state/request table, entry by entry, against
                       the specification, with the "undefined" entries
                       treated as REJECT rather than as don't-care
    FAILURE SIGNATURE  a device that answers a question it should not have
                       understood, returning a value it cannot have
    FALSE CONFIDENCE   "it enumerates" -- enumeration is one path
    NEXT               ask what the device would RETURN, and whether that
                       value exists yet

The table this specimen encodes, in the three device states that matter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    REQUEST              DEFAULT      ADDRESS       CONFIGURED
    -------------------  -----------  ------------  -----------
    GET_STATUS           undefined    yes (if 0)    yes
    CLEAR_FEATURE        undefined    yes (if 0)    yes
    SET_FEATURE          undefined    yes (if 0)    yes
    SET_ADDRESS          yes          yes           yes
    GET_DESCRIPTOR       yes          yes           yes
    SET_DESCRIPTOR       yes          yes           yes
    GET_CONFIGURATION    undefined    yes           yes
    SET_CONFIGURATION    undefined    yes           yes
    GET_INTERFACE        undefined    undefined     yes
    SET_INTERFACE        undefined    undefined     yes
    SYNCH_FRAME          undefined    undefined     yes

    "if 0"  in the Address state the interface- and endpoint-directed
            forms apply only to interface zero and endpoint zero,
            because no others exist until a configuration is selected.

5. Question — Are The Field Bounds Right?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every field in the request has a legal range, and
                       the design enforces the range the specification
                       states rather than the range the field is wide
                       enough to hold
    EVIDENCE           each field, its width, its legal range, and the
                       line that enforces it
    FAILURE SIGNATURE  accepting a value that cannot mean anything, then
                       acting on it
    FALSE CONFIDENCE   "the field is 16 bits so any 16-bit value is fine"
    NEXT               ask what the device does with the out-of-range
                       value it just accepted

Two in this specimen are worth the ink:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    SET_ADDRESS   wValue carries the address. The address FIELD on the bus
                  is seven bits, so the legal range is 0..127 even though
                  wValue is sixteen bits wide. Mutation B-M2 accepts up to
                  255; caught by three directed checks, and by no host,
                  because no host sends 200.

    SET_CONFIG    wValue selects a configuration. Zero is legal and means
                  "return to the Address state" -- a value that is easy to
                  read as an error and reject, which breaks a host that is
                  deliberately unconfiguring the device.

The second is the better lesson: a bounds check that is too strict is also a conformance defect, and it is the one review misses because rejecting things feels safe.

6. Question — Does The Data Stage Match The Request?

A GET returns data; a SET does not. The direction bit in the request type must agree with the request, and a mismatch is a malformed request rather than an unusual one.

Mutation B-M3 removes the direction check entirely. It scores 167 directed failures — by far the largest in this specimen — because the exhaustive sweep drives both directions against every request, so every GET with the wrong direction bit and every SET with the wrong one is a separate failure.

That number is worth contrasting with B-M1's three. Both are conformance defects. One is caught 167 times because the sweep has direction as an axis; the other is caught three times because it is one cell of one table. Mutation scores measure the shape of the stimulus at least as much as the severity of the defect, and a reviewer who ranks findings by mutation score has ranked them by how easy they were to test.

7. Question — Is This Ours To Reject?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          the block decides only the questions its scope
                       covers, and says so for the rest
    EVIDENCE           what the design does with a request type it does
                       not implement
    FAILURE SIGNATURE  a class request rejected by the Chapter 9 layer,
                       so the class driver never sees it and the feature
                       silently does not exist
    FALSE CONFIDENCE   "we reject what we do not recognise" -- safe for
                       standard requests, wrong for class ones
    NEXT               ask who DOES decide, and whether they exist yet

The specimen returns a distinct verdict for a class or vendor request: not must_handle, and explicitly not must_stall either.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign must_stall  = (reason != OK) && (reason != E_NOT_STANDARD);

A device with no handler for that class stalls it; a device with one does not; and neither is a Chapter 9 question. Mutation B-M5 collapses the distinction and stalls everything non-standard. It scores one directed failure — the single scenario written for it — which makes it the thinnest score in the module and a good illustration of why thin scores need attention rather than celebration.

8. SystemVerilog And VHDL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_req_legal_sv -- the same Chapter 9 legality table, in
//  SystemVerilog. Identical contract to usb_req_legal.v: same ports,
//  same rule order, same verdicts, same reason codes.
//
//  A decision table is exactly where a REVIEWER wants the language to
//  help, and here it does two things the Verilog version cannot.
//
//  The request codes are an ENUM, so a table entry for a code that is
//  not a standard request is a compile error rather than a silent
//  branch nobody will ever take.
//
//  The per-request attributes are looked up in ONE unique case, so the
//  tool reports an overlapping or missing entry. In the Verilog version
//  the same information is spread across five separate conditional
//  cascades -- five places to forget the same request, and no tool that
//  will say so. That is a review finding waiting to happen, and it is a
//  fair criticism of the Verilog version rather than of Verilog.
// =====================================================================
module usb_req_legal_sv (
  input  logic [1:0]  dev_state,
  input  logic        dir_in,
  input  logic [1:0]  recipient,
  input  logic [1:0]  req_type,
  input  logic [7:0]  b_request,
  input  logic [15:0] w_value,
  input  logic [15:0] w_index,
  input  logic [15:0] w_length,
  input  logic [7:0]  n_configs,
  input  logic [7:0]  n_interfaces,
  input  logic [3:0]  n_endpoints,
  output logic        must_handle,
  output logic        must_stall,
  output logic [3:0]  reason
);

  typedef enum logic [7:0] {
    REQ_GET_STATUS  = 8'd0,
    REQ_CLEAR_FEAT  = 8'd1,
    REQ_SET_FEAT    = 8'd3,
    REQ_SET_ADDRESS = 8'd5,
    REQ_GET_DESC    = 8'd6,
    REQ_SET_DESC    = 8'd7,
    REQ_GET_CONFIG  = 8'd8,
    REQ_SET_CONFIG  = 8'd9,
    REQ_GET_IFACE   = 8'd10,
    REQ_SET_IFACE   = 8'd11,
    REQ_SYNCH_FRAME = 8'd12
  } req_e;

  typedef enum logic [1:0] {
    ST_DEFAULT = 2'd0, ST_ADDRESS = 2'd1, ST_CONFIGURED = 2'd2
  } state_e;

  typedef enum logic [1:0] {
    RCP_DEVICE = 2'd0, RCP_IFACE = 2'd1, RCP_ENDPOINT = 2'd2
  } recip_e;

  localparam logic [3:0] OK             = 4'd0, E_NOT_STANDARD = 4'd1,
                         E_UNKNOWN_REQ  = 4'd2, E_WRONG_STATE  = 4'd3,
                         E_BAD_DIR      = 4'd4, E_BAD_RECIP    = 4'd5,
                         E_BAD_INDEX    = 4'd6, E_BAD_VALUE    = 4'd7,
                         E_BAD_LENGTH   = 4'd8;

  // ---- ONE lookup, so there is one place to forget a request ----
  logic       known, wants_in, zero_len;
  logic [2:0] recip_mask;    // bit0 DEVICE, bit1 INTERFACE, bit2 ENDPOINT
  logic [2:0] state_mask;    // bit0 DEFAULT, bit1 ADDRESS, bit2 CONFIGURED

  always_comb begin
    known = 1'b1; wants_in = 1'b0; zero_len = 1'b0;
    recip_mask = 3'b001; state_mask = 3'b110;
    unique case (b_request)
      REQ_GET_STATUS:  begin wants_in=1'b1; recip_mask=3'b111; state_mask=3'b110; end
      REQ_CLEAR_FEAT:  begin zero_len=1'b1; recip_mask=3'b111; state_mask=3'b110; end
      REQ_SET_FEAT:    begin zero_len=1'b1; recip_mask=3'b111; state_mask=3'b110; end
      REQ_SET_ADDRESS: begin zero_len=1'b1; recip_mask=3'b001; state_mask=3'b111; end
      REQ_GET_DESC:    begin wants_in=1'b1; recip_mask=3'b001; state_mask=3'b111; end
      REQ_SET_DESC:    begin               recip_mask=3'b001; state_mask=3'b111; end
      REQ_GET_CONFIG:  begin wants_in=1'b1; recip_mask=3'b001; state_mask=3'b110; end
      REQ_SET_CONFIG:  begin zero_len=1'b1; recip_mask=3'b001; state_mask=3'b110; end
      REQ_GET_IFACE:   begin wants_in=1'b1; recip_mask=3'b010; state_mask=3'b100; end
      REQ_SET_IFACE:   begin zero_len=1'b1; recip_mask=3'b010; state_mask=3'b100; end
      REQ_SYNCH_FRAME: begin wants_in=1'b1; recip_mask=3'b100; state_mask=3'b100; end
      default:         known = 1'b0;
    endcase
  end

  logic iface_exists, ep_exists, index_ok, value_ok, length_ok;

  assign iface_exists = (dev_state == ST_CONFIGURED)
                        ? (w_index[7:0] <  n_interfaces)
                        : (w_index[7:0] == 8'd0);
  assign ep_exists    = (dev_state == ST_CONFIGURED)
                        ? (w_index[3:0] <= n_endpoints)
                        : (w_index[3:0] == 4'd0);
  assign index_ok     = (recipient == RCP_IFACE)    ? iface_exists :
                        (recipient == RCP_ENDPOINT) ? ep_exists    : 1'b1;
  assign value_ok     = (b_request == REQ_SET_ADDRESS) ? (w_value <= 16'd127)   :
                        (b_request == REQ_SET_CONFIG)  ? (w_value[7:0] <= n_configs)
                                                       : 1'b1;
  assign length_ok    = zero_len ? (w_length == 16'd0) : 1'b1;

  logic recip_ok, state_ok;
  assign recip_ok = (recipient <= RCP_ENDPOINT) && recip_mask[recipient];
  assign state_ok = (dev_state <= ST_CONFIGURED) && state_mask[dev_state];

  always_comb begin
    if      (req_type != 2'd0)     reason = E_NOT_STANDARD;
    else if (!known)               reason = E_UNKNOWN_REQ;
    else if (dir_in != wants_in)   reason = E_BAD_DIR;
    else if (!recip_ok)            reason = E_BAD_RECIP;
    else if (!state_ok)            reason = E_WRONG_STATE;
    else if (!index_ok)            reason = E_BAD_INDEX;
    else if (!value_ok)            reason = E_BAD_VALUE;
    else if (!length_ok)           reason = E_BAD_LENGTH;
    else                           reason = OK;
  end

  assign must_handle = (reason == OK);
  assign must_stall  = (reason != OK) && (reason != E_NOT_STANDARD);

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  usb_req_legal (VHDL-2008) -- the same Chapter 9 legality table.
--  Identical contract to the Verilog and SystemVerilog versions.
--
--  A decision table is where VHDL's typing earns the most in review.
--  The request code arrives as a vector, is decoded ONCE into an
--  enumerated type, and every table afterwards is a case over that
--  type -- which the compiler checks for completeness. A forgotten
--  request is a compile error here, a silent default in Verilog, and a
--  tool warning in SystemVerilog.
--
--  The `others` branch on the DECODE is the only place a vector is
--  switched on, and it is the honest one: the eight-bit field really
--  does have 245 values that are not standard requests.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity usb_req_legal is
  port (
    dev_state    : in  unsigned(1 downto 0);
    dir_in       : in  std_logic;
    recipient    : in  unsigned(1 downto 0);
    req_type     : in  unsigned(1 downto 0);
    b_request    : in  unsigned(7 downto 0);
    w_value      : in  unsigned(15 downto 0);
    w_index      : in  unsigned(15 downto 0);
    w_length     : in  unsigned(15 downto 0);
    n_configs    : in  unsigned(7 downto 0);
    n_interfaces : in  unsigned(7 downto 0);
    n_endpoints  : in  unsigned(3 downto 0);
    must_handle  : out std_logic;
    must_stall   : out std_logic;
    reason       : out unsigned(3 downto 0)
  );
end entity usb_req_legal;

architecture rtl of usb_req_legal is

  type req_t is (
    REQ_GET_STATUS, REQ_CLEAR_FEAT, REQ_SET_FEAT, REQ_SET_ADDRESS,
    REQ_GET_DESC, REQ_SET_DESC, REQ_GET_CONFIG, REQ_SET_CONFIG,
    REQ_GET_IFACE, REQ_SET_IFACE, REQ_SYNCH_FRAME, REQ_UNKNOWN
  );

  -- One record per request, so the eleven facts about a request live in
  -- one row rather than in five separate cascades.
  type req_attr_t is record
    wants_in   : std_logic;
    zero_len   : std_logic;
    recip_mask : std_logic_vector(2 downto 0);  -- DEVICE / IFACE / ENDPOINT
    state_mask : std_logic_vector(2 downto 0);  -- DEFAULT / ADDRESS / CONFIGURED
  end record req_attr_t;

  signal req      : req_t;
  signal attr     : req_attr_t;
  signal known    : std_logic;
  signal idx_ok, val_ok, len_ok, recip_ok, state_ok : std_logic;
  signal rsn      : unsigned(3 downto 0);

  constant OK_C             : unsigned(3 downto 0) := to_unsigned(0, 4);
  constant E_NOT_STANDARD_C : unsigned(3 downto 0) := to_unsigned(1, 4);
  constant E_UNKNOWN_REQ_C  : unsigned(3 downto 0) := to_unsigned(2, 4);
  constant E_WRONG_STATE_C  : unsigned(3 downto 0) := to_unsigned(3, 4);
  constant E_BAD_DIR_C      : unsigned(3 downto 0) := to_unsigned(4, 4);
  constant E_BAD_RECIP_C    : unsigned(3 downto 0) := to_unsigned(5, 4);
  constant E_BAD_INDEX_C    : unsigned(3 downto 0) := to_unsigned(6, 4);
  constant E_BAD_VALUE_C    : unsigned(3 downto 0) := to_unsigned(7, 4);
  constant E_BAD_LENGTH_C   : unsigned(3 downto 0) := to_unsigned(8, 4);

begin

  -- The ONE place a vector is switched on, and its `others` is real.
  decode : process (b_request)
  begin
    case to_integer(b_request) is
      when 0      => req <= REQ_GET_STATUS;
      when 1      => req <= REQ_CLEAR_FEAT;
      when 3      => req <= REQ_SET_FEAT;
      when 5      => req <= REQ_SET_ADDRESS;
      when 6      => req <= REQ_GET_DESC;
      when 7      => req <= REQ_SET_DESC;
      when 8      => req <= REQ_GET_CONFIG;
      when 9      => req <= REQ_SET_CONFIG;
      when 10     => req <= REQ_GET_IFACE;
      when 11     => req <= REQ_SET_IFACE;
      when 12     => req <= REQ_SYNCH_FRAME;
      when others => req <= REQ_UNKNOWN;
    end case;
  end process decode;

  -- A case over an ENUMERATED type. Remove a branch and it will not
  -- compile, which is the property the other two languages lack.
  attrs : process (req)
  begin
    case req is
      when REQ_GET_STATUS  => attr <= ('1','0',"111","110");
      when REQ_CLEAR_FEAT  => attr <= ('0','1',"111","110");
      when REQ_SET_FEAT    => attr <= ('0','1',"111","110");
      when REQ_SET_ADDRESS => attr <= ('0','1',"001","111");
      when REQ_GET_DESC    => attr <= ('1','0',"001","111");
      when REQ_SET_DESC    => attr <= ('0','0',"001","111");
      when REQ_GET_CONFIG  => attr <= ('1','0',"001","110");
      when REQ_SET_CONFIG  => attr <= ('0','1',"001","110");
      when REQ_GET_IFACE   => attr <= ('1','0',"010","100");
      when REQ_SET_IFACE   => attr <= ('0','1',"010","100");
      when REQ_SYNCH_FRAME => attr <= ('1','0',"100","100");
      when REQ_UNKNOWN     => attr <= ('0','0',"000","000");
    end case;
  end process attrs;

  known <= '0' when req = REQ_UNKNOWN else '1';

  idx_ok <= '1' when recipient = 1 and dev_state = 2 and
                     w_index(7 downto 0) < n_interfaces else
            '1' when recipient = 1 and dev_state /= 2 and
                     w_index(7 downto 0) = 0 else
            '1' when recipient = 2 and dev_state = 2 and
                     w_index(3 downto 0) <= n_endpoints else
            '1' when recipient = 2 and dev_state /= 2 and
                     w_index(3 downto 0) = 0 else
            '1' when recipient = 0 else
            '0';

  val_ok <= '0' when req = REQ_SET_ADDRESS and w_value > 127 else
            '0' when req = REQ_SET_CONFIG  and w_value(7 downto 0) > n_configs
            else '1';

  len_ok <= '0' when attr.zero_len = '1' and w_length /= 0 else '1';

  recip_ok <= '0' when recipient > 2 else attr.recip_mask(to_integer(recipient));
  state_ok <= '0' when dev_state > 2 else attr.state_mask(to_integer(dev_state));

  rsn <= E_NOT_STANDARD_C when req_type /= 0        else
         E_UNKNOWN_REQ_C  when known    = '0'       else
         E_BAD_DIR_C      when dir_in  /= attr.wants_in else
         E_BAD_RECIP_C    when recip_ok = '0'       else
         E_WRONG_STATE_C  when state_ok = '0'       else
         E_BAD_INDEX_C    when idx_ok   = '0'       else
         E_BAD_VALUE_C    when val_ok   = '0'       else
         E_BAD_LENGTH_C   when len_ok   = '0'       else
         OK_C;

  reason      <= rsn;
  must_handle <= '1' when rsn = OK_C else '0';
  must_stall  <= '1' when (rsn /= OK_C and rsn /= E_NOT_STANDARD_C) else '0';

end architecture rtl;

9. The Exhaustive Sweep, And Its Denominator

A legality table is one of the rare things in hardware that can be checked completely, and when something can be exhausted the review item is to make sure the denominator is honest.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    device states      DEFAULT, ADDRESS, CONFIGURED                    3
    recipients         DEVICE, INTERFACE, ENDPOINT                     3
    request codes      0..15: the eleven standard requests, plus five
                       undefined ones -- which is the case the
                       specification is most often got wrong about    16
    directions         device-to-host, host-to-device                  2
    ------------------------------------------------------------------
                                              3 x 3 x 16 x 2 =       288

and the exclusions, named:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    recipient 3 is reserved, and a sweep including it would measure the
    reserved encoding rather than the table

    wValue, wIndex and wLength are held at LEGAL values in this phase so
    that the state, recipient and direction rules are what is being
    measured. Their bounds are a separate phase, one scenario each.

288 verdicts, three checks each, all compared against a reference table written independently of the design's structure. The results:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                          VERILOG   SYSTEMVERILOG   VHDL-2008
    phase 1 exhaustive        864           864          864
    phase 2 bounds            903           903          903
    ---- DIRECTED             903           903          903
    errors                      0             0            0
    TOTAL                  60,903        60,903       60,903

and, more usefully than the totals, the verdict classes the sweep actually reached:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    must handle ............ 1,018
    must stall ............. 17,246
      undefined in state ... 1,038
      wrong direction ...... 6,312
      wrong recipient ...... 3,014
      index out of range ... 665
      value out of range ... 200
      length wrong ......... 212
      not a defined request  5,805
    not a standard request . 2,037

Every reason code is reached. That is the reachability item from 30.2 applied to a decision table: a verdict class with a count of zero is a rule that has never been exercised, and in a conformance table that is precisely the rule that will be wrong.

10. Mutation As Conformance Evidence

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    MUT    REVIEW QUESTION                              V-DIR  SV-DIR  VH-DIR
    B-M1   is this request DEFINED in this state?           3       3       3
    B-M2   is the address field really seven bits?          3       3       3
    B-M3   does the data stage match the request?         167     167     167
    B-M4   does endpoint N exist?                           3       3       3
    B-M5   is a class request Chapter 9's to reject?        1       1       1

BASE reads zero in all nine columns and every directed column is identical across the three languages.

Four of the five score one or three. That is not weakness; it is the arithmetic of a table. A single wrong cell is wrong in exactly the situations that reach that cell, and a sweep visits each cell once. A conformance defect is intrinsically low-yield to test and high-consequence to ship, which is the opposite of most RTL defects and is the reason conformance gets reviewed rather than fuzzed.

The practical consequence for a reviewer: in a conformance table, do not use mutation scores to rank findings. Use them to confirm that each cell is reached at all.

11. The Testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  tb_usb_req_legal -- Verilog-2005 testbench for usb_req_legal.
//
//  The reference model here is written as a TABLE, not as the same
//  cascade of conditions the design uses. That is deliberate and it is
//  the review question from 30.2 applied to this block: a checker built
//  the same way as the design reproduces the design's misreading of the
//  specification along with it, and then agrees with it.
//
//  PHASES
//    1 EXHAUSTIVE  3 states x 3 recipients x 16 request codes x 2
//                  directions = 288 verdicts, every one compared
//    2 BOUNDARY    the value, index and length limits, one each
//    3 RANDOM      supplementary, over the whole field space
// =====================================================================
`timescale 1ns/1ps

module tb_usb_req_legal;

  reg  [1:0]  dev_state;
  reg         dir_in;
  reg  [1:0]  recipient, req_type;
  reg  [7:0]  b_request;
  reg  [15:0] w_value, w_index, w_length;
  reg  [7:0]  n_configs, n_interfaces;
  reg  [3:0]  n_endpoints;

  wire        must_handle, must_stall;
  wire [3:0]  reason;

  usb_req_legal dut (
    .dev_state(dev_state), .dir_in(dir_in), .recipient(recipient),
    .req_type(req_type), .b_request(b_request),
    .w_value(w_value), .w_index(w_index), .w_length(w_length),
    .n_configs(n_configs), .n_interfaces(n_interfaces),
    .n_endpoints(n_endpoints),
    .must_handle(must_handle), .must_stall(must_stall), .reason(reason)
  );

  integer chk_dir, chk_rnd, err, in_random;
  integer m_handle, m_stall, m_notstd, m_state, m_dir, m_recip,
          m_index, m_value, m_length, m_unknown;
  integer st, rc, rq, di, i;

  task bump; begin
    if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
  end endtask

  // -----------------------------------------------------------------
  //  The reference model: a table lookup, structurally unlike the DUT.
  //  ref_reason returns the code the specification requires.
  // -----------------------------------------------------------------
  function [3:0] ref_reason;
    input [1:0]  st_i;
    input        dir_i;
    input [1:0]  rcp_i;
    input [1:0]  typ_i;
    input [7:0]  req_i;
    input [15:0] val_i, idx_i, len_i;
    input [7:0]  ncfg, nif;
    input [3:0]  nep;
    // per-request attributes, looked up rather than recomputed
    reg       known, wants_in, zero_len;
    reg [2:0] recip_mask;      // bit0 DEVICE, bit1 INTERFACE, bit2 ENDPOINT
    reg [2:0] state_mask;      // bit0 DEFAULT, bit1 ADDRESS, bit2 CONFIGURED
    reg       idx_ok, val_ok;
    begin
      known = 1'b1; wants_in = 1'b0; zero_len = 1'b0;
      recip_mask = 3'b001; state_mask = 3'b110;
      case (req_i)
        8'd0:  begin wants_in=1; recip_mask=3'b111; state_mask=3'b110; end // GET_STATUS
        8'd1:  begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // CLEAR_FEATURE
        8'd3:  begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // SET_FEATURE
        8'd5:  begin zero_len=1; recip_mask=3'b001; state_mask=3'b111; end // SET_ADDRESS
        8'd6:  begin wants_in=1; recip_mask=3'b001; state_mask=3'b111; end // GET_DESCRIPTOR
        8'd7:  begin              recip_mask=3'b001; state_mask=3'b111; end // SET_DESCRIPTOR
        8'd8:  begin wants_in=1; recip_mask=3'b001; state_mask=3'b110; end // GET_CONFIGURATION
        8'd9:  begin zero_len=1; recip_mask=3'b001; state_mask=3'b110; end // SET_CONFIGURATION
        8'd10: begin wants_in=1; recip_mask=3'b010; state_mask=3'b100; end // GET_INTERFACE
        8'd11: begin zero_len=1; recip_mask=3'b010; state_mask=3'b100; end // SET_INTERFACE
        8'd12: begin wants_in=1; recip_mask=3'b100; state_mask=3'b100; end // SYNCH_FRAME
        default: known = 1'b0;
      endcase

      if (rcp_i == 2'd1)
        idx_ok = (st_i == 2'd2) ? (idx_i[7:0] < nif) : (idx_i[7:0] == 8'd0);
      else if (rcp_i == 2'd2)
        idx_ok = (st_i == 2'd2) ? (idx_i[3:0] <= nep) : (idx_i[3:0] == 4'd0);
      else
        idx_ok = 1'b1;

      if (req_i == 8'd5)      val_ok = (val_i <= 16'd127);
      else if (req_i == 8'd9) val_ok = (val_i[7:0] <= ncfg);
      else                    val_ok = 1'b1;

      if      (typ_i != 2'd0)                        ref_reason = 4'd1;
      else if (!known)                               ref_reason = 4'd2;
      else if (dir_i !== wants_in)                   ref_reason = 4'd4;
      else if (!recip_mask[rcp_i] || rcp_i == 2'd3)  ref_reason = 4'd5;
      else if (!state_mask[st_i])                    ref_reason = 4'd3;
      else if (!idx_ok)                              ref_reason = 4'd6;
      else if (!val_ok)                              ref_reason = 4'd7;
      else if (zero_len && (len_i != 16'd0))         ref_reason = 4'd8;
      else                                           ref_reason = 4'd0;
    end
  endfunction

  task check_now;
    input [255:0] what;
    reg [3:0] exp_r;
    reg exp_h, exp_s;
    begin
      #1;
      exp_r = ref_reason(dev_state, dir_in, recipient, req_type, b_request,
                         w_value, w_index, w_length,
                         n_configs, n_interfaces, n_endpoints);
      exp_h = (exp_r == 4'd0);
      exp_s = (exp_r != 4'd0) && (exp_r != 4'd1);
      bump;
      if (reason !== exp_r) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %0s reason: got %0d expected %0d  (state=%0d rcp=%0d req=%0d dir=%0d)",
                   what, reason, exp_r, dev_state, recipient, b_request, dir_in);
      end
      bump;
      if (must_handle !== exp_h) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %0s must_handle: got %0d expected %0d", what, must_handle, exp_h);
      end
      bump;
      if (must_stall !== exp_s) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %0s must_stall: got %0d expected %0d", what, must_stall, exp_s);
      end
      // tallies -- what this sweep actually reached
      case (exp_r)
        4'd0: m_handle  = m_handle  + 1;
        4'd1: m_notstd  = m_notstd  + 1;
        4'd2: m_unknown = m_unknown + 1;
        4'd3: m_state   = m_state   + 1;
        4'd4: m_dir     = m_dir     + 1;
        4'd5: m_recip   = m_recip   + 1;
        4'd6: m_index   = m_index   + 1;
        4'd7: m_value   = m_value   + 1;
        4'd8: m_length  = m_length  + 1;
      endcase
      if (exp_s) m_stall = m_stall + 1;
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 1 -- the exhaustive sweep.
  //
  //  DENOMINATOR, derived:
  //    device states      DEFAULT, ADDRESS, CONFIGURED              3
  //    recipients         DEVICE, INTERFACE, ENDPOINT               3
  //    request codes      0..15: the eleven standard requests plus
  //                       five undefined ones, which is the case the
  //                       specification is most often wrong about    16
  //    directions         device-to-host, host-to-device             2
  //    ------------------------------------------------------------------
  //                                              3 x 3 x 16 x 2 =  288
  //
  //  Recipient 3 is excluded: it is reserved, and a sweep that included
  //  it would be measuring the reserved encoding rather than the table.
  //  wValue, wIndex and wLength are held at LEGAL values here so that
  //  the state/recipient/direction rules are what is being measured;
  //  their bounds are phase 2, one scenario each.
  // -----------------------------------------------------------------
  task phase_sweep;
    begin
      req_type     = 2'd0;
      n_configs    = 8'd2;
      n_interfaces = 8'd3;
      n_endpoints  = 4'd4;
      w_value      = 16'd0;
      w_index      = 16'd0;
      w_length     = 16'd0;
      for (st = 0; st < 3; st = st + 1)
      for (rc = 0; rc < 3; rc = rc + 1)
      for (rq = 0; rq < 16; rq = rq + 1)
      for (di = 0; di < 2; di = di + 1) begin
        dev_state = st[1:0];
        recipient = rc[1:0];
        b_request = rq[7:0];
        dir_in    = di[0];
        // GET_DESCRIPTOR and GET_STATUS legitimately carry a length
        w_length  = (rq == 0 || rq == 6 || rq == 8 || rq == 10 || rq == 12)
                    ? 16'd8 : 16'd0;
        check_now("sweep");
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 2 -- the bounds, one scenario each.
  // -----------------------------------------------------------------
  task phase_bounds;
    begin
      req_type = 2'd0; n_configs = 8'd2; n_interfaces = 8'd3; n_endpoints = 4'd4;

      // SET_ADDRESS: the address field is seven bits wide
      dev_state=2'd1; recipient=2'd0; b_request=8'd5; dir_in=1'b0;
      w_index=0; w_length=0;
      w_value = 16'd127; check_now("addr 127 legal");
      w_value = 16'd128; check_now("addr 128 illegal");

      // SET_CONFIGURATION: zero is legal and means "unconfigure"
      dev_state=2'd2; recipient=2'd0; b_request=8'd9; dir_in=1'b0;
      w_value = 16'd0; check_now("config 0 legal");
      w_value = 16'd2; check_now("config 2 legal");
      w_value = 16'd3; check_now("config 3 illegal");

      // a no-data-stage request that asks for data
      w_value = 16'd1; w_length = 16'd8; check_now("set-config with length");
      w_length = 16'd0;

      // interface index bounds, and the Address-state special case
      dev_state=2'd2; recipient=2'd1; b_request=8'd10; dir_in=1'b1;
      w_length=16'd1;
      w_index = 16'd2; check_now("iface 2 exists");
      w_index = 16'd3; check_now("iface 3 does not");
      dev_state=2'd1; recipient=2'd1; b_request=8'd0; dir_in=1'b1;
      w_length=16'd2;
      w_index = 16'd0; check_now("iface 0 in address state");
      w_index = 16'd1; check_now("iface 1 in address state");

      // endpoint index bounds
      dev_state=2'd2; recipient=2'd2; b_request=8'd0; dir_in=1'b1;
      w_length=16'd2;
      w_index = 16'd4; check_now("ep 4 exists");
      w_index = 16'd5; check_now("ep 5 does not");

      // a class request is not this block's to reject
      dev_state=2'd2; recipient=2'd1; req_type=2'd1; b_request=8'd10;
      dir_in=1'b1; w_index=16'd0; w_length=16'd1;
      check_now("class request passes through");
      req_type = 2'd0;
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 3 -- random over the whole field space.
  // -----------------------------------------------------------------
  task phase_random;
    begin
      in_random = 1;
      for (i = 0; i < 20000; i = i + 1) begin
        dev_state    = ({$random} % 3);
        recipient    = ({$random} % 3);
        req_type     = (({$random} % 100) < 85) ? 2'd0 : ({$random} % 3);
        b_request    = (({$random} % 100) < 80) ? ({$random} % 13)
                                                : ({$random} % 256);
        dir_in       = ({$random} % 2);
        w_value      = (({$random} % 100) < 60) ? ({$random} % 132)
                                                : ({$random} % 65536);
        w_index      = (({$random} % 100) < 70) ? ({$random} % 8)
                                                : ({$random} % 65536);
        w_length     = (({$random} % 100) < 50) ? 16'd0 : ({$random} % 64);
        n_configs    = ({$random} % 4) + 8'd1;
        n_interfaces = ({$random} % 4) + 8'd1;
        n_endpoints  = ({$random} % 8);
        check_now("random");
      end
      in_random = 0;
    end
  endtask

  initial begin
    chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
    m_handle=0; m_stall=0; m_notstd=0; m_state=0; m_dir=0; m_recip=0;
    m_index=0; m_value=0; m_length=0; m_unknown=0;

    phase_sweep;
    $display("  phase 1 exhaustive  : %0d checks, %0d errors  (288 verdicts)",
             chk_dir, err);
    phase_bounds;
    $display("  phase 2 bounds      : %0d checks, %0d errors", chk_dir, err);
    $display("  ---- DIRECTED-ONLY  : %0d checks, %0d errors ----", chk_dir, err);
    phase_random;

    $display("");
    $display("  verdicts reached (all phases)");
    $display("    must handle ............ %0d", m_handle);
    $display("    must stall ............. %0d", m_stall);
    $display("      undefined in state ... %0d", m_state);
    $display("      wrong direction ...... %0d", m_dir);
    $display("      wrong recipient ...... %0d", m_recip);
    $display("      index out of range ... %0d", m_index);
    $display("      value out of range ... %0d", m_value);
    $display("      length wrong ......... %0d", m_length);
    $display("      not a defined request  %0d", m_unknown);
    $display("    not a standard request . %0d", m_notstd);
    $display("");
    $display("  directed checks ........ %0d", chk_dir);
    $display("  random checks .......... %0d", chk_rnd);
    $display("  TOTAL checks ........... %0d", chk_dir + chk_rnd);
    $display("  ERRORS ................. %0d", err);
    if (err == 0) $display("  PASS"); else $display("  FAIL");
    $finish;
  end

endmodule

The same bench in the other two languages. The reference table in each is written as a lookup rather than as the design's cascade of conditions — which is 30.2's independence item applied to a transcription: a checker that walks the specification the same way the design does will misread it the same way.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  tb_usb_req_legal_sv -- SystemVerilog testbench for usb_req_legal_sv.
//
//  Phases 1-2 present the SAME directed stimulus as the Verilog bench,
//  so their directed check counts must agree to the digit.
//
//  The reference model here is written as a TABLE, not as the same
//  cascade of conditions the design uses. That is deliberate and it is
//  the review question from 30.2 applied to this block: a checker built
//  the same way as the design reproduces the design's misreading of the
//  specification along with it, and then agrees with it.
//
//  PHASES
//    1 EXHAUSTIVE  3 states x 3 recipients x 16 request codes x 2
//                  directions = 288 verdicts, every one compared
//    2 BOUNDARY    the value, index and length limits, one each
//    3 RANDOM      supplementary, over the whole field space
// =====================================================================
`timescale 1ns/1ps

module tb_usb_req_legal_sv;

  logic [1:0]  dev_state;
  logic        dir_in;
  logic [1:0]  recipient, req_type;
  logic [7:0]  b_request;
  logic [15:0] w_value, w_index, w_length;
  logic [7:0]  n_configs, n_interfaces;
  logic [3:0]  n_endpoints;

  wire        must_handle, must_stall;
  wire [3:0]  reason;

  usb_req_legal_sv dut (
    .dev_state(dev_state), .dir_in(dir_in), .recipient(recipient),
    .req_type(req_type), .b_request(b_request),
    .w_value(w_value), .w_index(w_index), .w_length(w_length),
    .n_configs(n_configs), .n_interfaces(n_interfaces),
    .n_endpoints(n_endpoints),
    .must_handle(must_handle), .must_stall(must_stall), .reason(reason)
  );

  int  chk_dir, chk_rnd, err;
  bit  in_random;
  int  m_handle, m_stall, m_notstd, m_state, m_dir, m_recip,
       m_index, m_value, m_length, m_unknown;
  int  st, rc, rq, di, i;

  task bump; begin
    if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
  end endtask

  // -----------------------------------------------------------------
  //  The reference model: a table lookup, structurally unlike the DUT.
  //  ref_reason returns the code the specification requires.
  // -----------------------------------------------------------------
  function [3:0] ref_reason;
    input [1:0]  st_i;
    input        dir_i;
    input [1:0]  rcp_i;
    input [1:0]  typ_i;
    input [7:0]  req_i;
    input [15:0] val_i, idx_i, len_i;
    input [7:0]  ncfg, nif;
    input [3:0]  nep;
    // per-request attributes, looked up rather than recomputed
    logic       known, wants_in, zero_len;
    logic [2:0] recip_mask;    // bit0 DEVICE, bit1 INTERFACE, bit2 ENDPOINT
    logic [2:0] state_mask;    // bit0 DEFAULT, bit1 ADDRESS, bit2 CONFIGURED
    logic       idx_ok, val_ok;
    begin
      known = 1'b1; wants_in = 1'b0; zero_len = 1'b0;
      recip_mask = 3'b001; state_mask = 3'b110;
      case (req_i)
        8'd0:  begin wants_in=1; recip_mask=3'b111; state_mask=3'b110; end // GET_STATUS
        8'd1:  begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // CLEAR_FEATURE
        8'd3:  begin zero_len=1; recip_mask=3'b111; state_mask=3'b110; end // SET_FEATURE
        8'd5:  begin zero_len=1; recip_mask=3'b001; state_mask=3'b111; end // SET_ADDRESS
        8'd6:  begin wants_in=1; recip_mask=3'b001; state_mask=3'b111; end // GET_DESCRIPTOR
        8'd7:  begin              recip_mask=3'b001; state_mask=3'b111; end // SET_DESCRIPTOR
        8'd8:  begin wants_in=1; recip_mask=3'b001; state_mask=3'b110; end // GET_CONFIGURATION
        8'd9:  begin zero_len=1; recip_mask=3'b001; state_mask=3'b110; end // SET_CONFIGURATION
        8'd10: begin wants_in=1; recip_mask=3'b010; state_mask=3'b100; end // GET_INTERFACE
        8'd11: begin zero_len=1; recip_mask=3'b010; state_mask=3'b100; end // SET_INTERFACE
        8'd12: begin wants_in=1; recip_mask=3'b100; state_mask=3'b100; end // SYNCH_FRAME
        default: known = 1'b0;
      endcase

      if (rcp_i == 2'd1)
        idx_ok = (st_i == 2'd2) ? (idx_i[7:0] < nif) : (idx_i[7:0] == 8'd0);
      else if (rcp_i == 2'd2)
        idx_ok = (st_i == 2'd2) ? (idx_i[3:0] <= nep) : (idx_i[3:0] == 4'd0);
      else
        idx_ok = 1'b1;

      if (req_i == 8'd5)      val_ok = (val_i <= 16'd127);
      else if (req_i == 8'd9) val_ok = (val_i[7:0] <= ncfg);
      else                    val_ok = 1'b1;

      if      (typ_i != 2'd0)                        ref_reason = 4'd1;
      else if (!known)                               ref_reason = 4'd2;
      else if (dir_i !== wants_in)                   ref_reason = 4'd4;
      else if (!recip_mask[rcp_i] || rcp_i == 2'd3)  ref_reason = 4'd5;
      else if (!state_mask[st_i])                    ref_reason = 4'd3;
      else if (!idx_ok)                              ref_reason = 4'd6;
      else if (!val_ok)                              ref_reason = 4'd7;
      else if (zero_len && (len_i != 16'd0))         ref_reason = 4'd8;
      else                                           ref_reason = 4'd0;
    end
  endfunction

  task check_now(string what);
    logic [3:0] exp_r;
    logic exp_h, exp_s;
    begin
      #1;
      exp_r = ref_reason(dev_state, dir_in, recipient, req_type, b_request,
                         w_value, w_index, w_length,
                         n_configs, n_interfaces, n_endpoints);
      exp_h = (exp_r == 4'd0);
      exp_s = (exp_r != 4'd0) && (exp_r != 4'd1);
      bump;
      if (reason !== exp_r) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %s reason: got %0d expected %0d  (state=%0d rcp=%0d req=%0d dir=%0d)",
                   what, reason, exp_r, dev_state, recipient, b_request, dir_in);
      end
      bump;
      if (must_handle !== exp_h) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %s must_handle: got %0d expected %0d", what, must_handle, exp_h);
      end
      bump;
      if (must_stall !== exp_s) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %s must_stall: got %0d expected %0d", what, must_stall, exp_s);
      end
      // tallies -- what this sweep actually reached
      case (exp_r)
        4'd0: m_handle  = m_handle  + 1;
        4'd1: m_notstd  = m_notstd  + 1;
        4'd2: m_unknown = m_unknown + 1;
        4'd3: m_state   = m_state   + 1;
        4'd4: m_dir     = m_dir     + 1;
        4'd5: m_recip   = m_recip   + 1;
        4'd6: m_index   = m_index   + 1;
        4'd7: m_value   = m_value   + 1;
        4'd8: m_length  = m_length  + 1;
      endcase
      if (exp_s) m_stall = m_stall + 1;
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 1 -- the exhaustive sweep.
  //
  //  DENOMINATOR, derived:
  //    device states      DEFAULT, ADDRESS, CONFIGURED              3
  //    recipients         DEVICE, INTERFACE, ENDPOINT               3
  //    request codes      0..15: the eleven standard requests plus
  //                       five undefined ones, which is the case the
  //                       specification is most often wrong about    16
  //    directions         device-to-host, host-to-device             2
  //    ------------------------------------------------------------------
  //                                              3 x 3 x 16 x 2 =  288
  //
  //  Recipient 3 is excluded: it is reserved, and a sweep that included
  //  it would be measuring the reserved encoding rather than the table.
  //  wValue, wIndex and wLength are held at LEGAL values here so that
  //  the state/recipient/direction rules are what is being measured;
  //  their bounds are phase 2, one scenario each.
  // -----------------------------------------------------------------
  task phase_sweep;
    begin
      req_type     = 2'd0;
      n_configs    = 8'd2;
      n_interfaces = 8'd3;
      n_endpoints  = 4'd4;
      w_value      = 16'd0;
      w_index      = 16'd0;
      w_length     = 16'd0;
      for (st = 0; st < 3; st = st + 1)
      for (rc = 0; rc < 3; rc = rc + 1)
      for (rq = 0; rq < 16; rq = rq + 1)
      for (di = 0; di < 2; di = di + 1) begin
        dev_state = 2'(st);
        recipient = 2'(rc);
        b_request = 8'(rq);
        dir_in    = 1'(di);
        // GET_DESCRIPTOR and GET_STATUS legitimately carry a length
        w_length  = (rq == 0 || rq == 6 || rq == 8 || rq == 10 || rq == 12)
                    ? 16'd8 : 16'd0;
        check_now("sweep");
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 2 -- the bounds, one scenario each.
  // -----------------------------------------------------------------
  task phase_bounds;
    begin
      req_type = 2'd0; n_configs = 8'd2; n_interfaces = 8'd3; n_endpoints = 4'd4;

      // SET_ADDRESS: the address field is seven bits wide
      dev_state=2'd1; recipient=2'd0; b_request=8'd5; dir_in=1'b0;
      w_index=0; w_length=0;
      w_value = 16'd127; check_now("addr 127 legal");
      w_value = 16'd128; check_now("addr 128 illegal");

      // SET_CONFIGURATION: zero is legal and means "unconfigure"
      dev_state=2'd2; recipient=2'd0; b_request=8'd9; dir_in=1'b0;
      w_value = 16'd0; check_now("config 0 legal");
      w_value = 16'd2; check_now("config 2 legal");
      w_value = 16'd3; check_now("config 3 illegal");

      // a no-data-stage request that asks for data
      w_value = 16'd1; w_length = 16'd8; check_now("set-config with length");
      w_length = 16'd0;

      // interface index bounds, and the Address-state special case
      dev_state=2'd2; recipient=2'd1; b_request=8'd10; dir_in=1'b1;
      w_length=16'd1;
      w_index = 16'd2; check_now("iface 2 exists");
      w_index = 16'd3; check_now("iface 3 does not");
      dev_state=2'd1; recipient=2'd1; b_request=8'd0; dir_in=1'b1;
      w_length=16'd2;
      w_index = 16'd0; check_now("iface 0 in address state");
      w_index = 16'd1; check_now("iface 1 in address state");

      // endpoint index bounds
      dev_state=2'd2; recipient=2'd2; b_request=8'd0; dir_in=1'b1;
      w_length=16'd2;
      w_index = 16'd4; check_now("ep 4 exists");
      w_index = 16'd5; check_now("ep 5 does not");

      // a class request is not this block's to reject
      dev_state=2'd2; recipient=2'd1; req_type=2'd1; b_request=8'd10;
      dir_in=1'b1; w_index=16'd0; w_length=16'd1;
      check_now("class request passes through");
      req_type = 2'd0;
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 3 -- random over the whole field space.
  // -----------------------------------------------------------------
  task phase_random;
    begin
      in_random = 1;
      for (i = 0; i < 20000; i = i + 1) begin
        dev_state    = ($urandom_range(2));
        recipient    = ($urandom_range(2));
        req_type     = (($urandom_range(99)) < 85) ? 2'd0 : ($urandom_range(2));
        b_request    = (($urandom_range(99)) < 80) ? ($urandom_range(12))
                                                : ($urandom_range(255));
        dir_in       = ($urandom_range(1));
        w_value      = (($urandom_range(99)) < 60) ? ($urandom_range(131))
                                                : ($urandom_range(65535));
        w_index      = (($urandom_range(99)) < 70) ? ($urandom_range(7))
                                                : ($urandom_range(65535));
        w_length     = (($urandom_range(99)) < 50) ? 16'd0 : ($urandom_range(63));
        n_configs    = ($urandom_range(3)) + 8'd1;
        n_interfaces = ($urandom_range(3)) + 8'd1;
        n_endpoints  = ($urandom_range(7));
        check_now("random");
      end
      in_random = 0;
    end
  endtask

  initial begin
    chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
    m_handle=0; m_stall=0; m_notstd=0; m_state=0; m_dir=0; m_recip=0;
    m_index=0; m_value=0; m_length=0; m_unknown=0;

    phase_sweep;
    $display("  phase 1 exhaustive  : %0d checks, %0d errors  (288 verdicts)",
             chk_dir, err);
    phase_bounds;
    $display("  phase 2 bounds      : %0d checks, %0d errors", chk_dir, err);
    $display("  ---- DIRECTED-ONLY  : %0d checks, %0d errors ----", chk_dir, err);
    phase_random;

    $display("");
    $display("  verdicts reached (all phases)");
    $display("    must handle ............ %0d", m_handle);
    $display("    must stall ............. %0d", m_stall);
    $display("      undefined in state ... %0d", m_state);
    $display("      wrong direction ...... %0d", m_dir);
    $display("      wrong recipient ...... %0d", m_recip);
    $display("      index out of range ... %0d", m_index);
    $display("      value out of range ... %0d", m_value);
    $display("      length wrong ......... %0d", m_length);
    $display("      not a defined request  %0d", m_unknown);
    $display("    not a standard request . %0d", m_notstd);
    $display("");
    $display("  directed checks ........ %0d", chk_dir);
    $display("  random checks .......... %0d", chk_rnd);
    $display("  TOTAL checks ........... %0d", chk_dir + chk_rnd);
    $display("  ERRORS ................. %0d", err);
    if (err == 0) $display("  PASS"); else $display("  FAIL");
    $finish;
  end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  tb_usb_req_legal -- VHDL-2008 testbench for usb_req_legal.
--
--  Phases 1-2 present the SAME directed stimulus as the Verilog and
--  SystemVerilog benches, so their directed check counts must agree.
--
--  The reference model is a table lookup written independently of the
--  design's structure, for the reason in 30.2: a checker built the same
--  way as the design reproduces the design's misreading of the
--  specification and then agrees with it.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

entity tb_usb_req_legal is
end entity tb_usb_req_legal;

architecture sim of tb_usb_req_legal is
  signal dev_state, recipient, req_type : unsigned(1 downto 0) := (others => '0');
  signal dir_in     : std_logic := '0';
  signal b_request  : unsigned(7 downto 0)  := (others => '0');
  signal w_value    : unsigned(15 downto 0) := (others => '0');
  signal w_index    : unsigned(15 downto 0) := (others => '0');
  signal w_length   : unsigned(15 downto 0) := (others => '0');
  signal n_configs, n_interfaces : unsigned(7 downto 0) := (others => '0');
  signal n_endpoints : unsigned(3 downto 0) := (others => '0');
  signal must_handle, must_stall : std_logic;
  signal reason : unsigned(3 downto 0);

  function b2i (s : std_logic) return integer is
  begin
    if s = '1' then return 1; else return 0; end if;
  end function b2i;
begin

  dut : entity work.usb_req_legal
    port map (dev_state => dev_state, dir_in => dir_in,
              recipient => recipient, req_type => req_type,
              b_request => b_request, w_value => w_value,
              w_index => w_index, w_length => w_length,
              n_configs => n_configs, n_interfaces => n_interfaces,
              n_endpoints => n_endpoints,
              must_handle => must_handle, must_stall => must_stall,
              reason => reason);

  stim : process
    variable chk_dir, chk_rnd, errs, shown : natural := 0;
    variable in_random : boolean := false;
    variable m_handle, m_stall, m_notstd, m_state, m_dirn, m_recip : natural := 0;
    variable m_index, m_value, m_length, m_unknown : natural := 0;
    variable seed1 : positive := 733_921;
    variable seed2 : positive := 55_411;

    procedure bump is
    begin
      if in_random then chk_rnd := chk_rnd + 1; else chk_dir := chk_dir + 1; end if;
    end procedure bump;

    -- The reference table, independent of the design's structure.
    impure function ref_reason return natural is
      variable known, wants_in, zero_len : boolean;
      variable rmask, smask : std_logic_vector(2 downto 0);
      variable idx_ok, val_ok : boolean;
    begin
      known := true; wants_in := false; zero_len := false;
      rmask := "001"; smask := "110";
      case to_integer(b_request) is
        when 0  => wants_in := true;  rmask := "111"; smask := "110";
        when 1  => zero_len := true;  rmask := "111"; smask := "110";
        when 3  => zero_len := true;  rmask := "111"; smask := "110";
        when 5  => zero_len := true;  rmask := "001"; smask := "111";
        when 6  => wants_in := true;  rmask := "001"; smask := "111";
        when 7  =>                    rmask := "001"; smask := "111";
        when 8  => wants_in := true;  rmask := "001"; smask := "110";
        when 9  => zero_len := true;  rmask := "001"; smask := "110";
        when 10 => wants_in := true;  rmask := "010"; smask := "100";
        when 11 => zero_len := true;  rmask := "010"; smask := "100";
        when 12 => wants_in := true;  rmask := "100"; smask := "100";
        when others => known := false;
      end case;

      if recipient = 1 then
        if dev_state = 2 then idx_ok := w_index(7 downto 0) < n_interfaces;
        else                  idx_ok := w_index(7 downto 0) = 0;
        end if;
      elsif recipient = 2 then
        if dev_state = 2 then idx_ok := w_index(3 downto 0) <= n_endpoints;
        else                  idx_ok := w_index(3 downto 0) = 0;
        end if;
      else idx_ok := true;
      end if;

      if to_integer(b_request) = 5 then    val_ok := w_value <= 127;
      elsif to_integer(b_request) = 9 then val_ok := w_value(7 downto 0) <= n_configs;
      else                                 val_ok := true;
      end if;

      if req_type /= 0 then return 1; end if;
      if not known then return 2; end if;
      if (dir_in = '1') /= wants_in then return 4; end if;
      if recipient > 2 or rmask(to_integer(recipient)) = '0' then return 5; end if;
      if smask(to_integer(dev_state)) = '0' then return 3; end if;
      if not idx_ok then return 6; end if;
      if not val_ok then return 7; end if;
      if zero_len and w_length /= 0 then return 8; end if;
      return 0;
    end function ref_reason;

    procedure check_now (what : string) is
      variable exp_r : natural;
      variable exp_h, exp_s : integer;
    begin
      wait for 1 ns;
      exp_r := ref_reason;
      if exp_r = 0 then exp_h := 1; else exp_h := 0; end if;
      if exp_r /= 0 and exp_r /= 1 then exp_s := 1; else exp_s := 0; end if;
      bump;
      if to_integer(reason) /= exp_r then
        errs := errs + 1;
        if (not in_random) and shown < 40 then
          shown := shown + 1;
          report "  ** " & what & " reason: got " &
                 integer'image(to_integer(reason)) & " expected " &
                 integer'image(exp_r) severity warning;
        end if;
      end if;
      bump;
      if b2i(must_handle) /= exp_h then
        errs := errs + 1;
        if (not in_random) and shown < 40 then
          shown := shown + 1;
          report "  ** " & what & " must_handle" severity warning;
        end if;
      end if;
      bump;
      if b2i(must_stall) /= exp_s then
        errs := errs + 1;
        if (not in_random) and shown < 40 then
          shown := shown + 1;
          report "  ** " & what & " must_stall" severity warning;
        end if;
      end if;
      case exp_r is
        when 0 => m_handle  := m_handle  + 1;
        when 1 => m_notstd  := m_notstd  + 1;
        when 2 => m_unknown := m_unknown + 1;
        when 3 => m_state   := m_state   + 1;
        when 4 => m_dirn    := m_dirn    + 1;
        when 5 => m_recip   := m_recip   + 1;
        when 6 => m_index   := m_index   + 1;
        when 7 => m_value   := m_value   + 1;
        when others => m_length := m_length + 1;
      end case;
      if exp_s = 1 then m_stall := m_stall + 1; end if;
    end procedure check_now;

    impure function rnd (n : positive) return natural is
      variable x : real;
    begin
      uniform(seed1, seed2, x);
      return natural(real(n - 1) * x);
    end function rnd;

  begin
    -- ---- PHASE 1 : 3 x 3 x 16 x 2 = 288 verdicts ----
    req_type <= "00"; n_configs <= to_unsigned(2, 8);
    n_interfaces <= to_unsigned(3, 8); n_endpoints <= to_unsigned(4, 4);
    w_value <= (others => '0'); w_index <= (others => '0');
    for st in 0 to 2 loop
      for rc in 0 to 2 loop
        for rq in 0 to 15 loop
          for di in 0 to 1 loop
            dev_state <= to_unsigned(st, 2);
            recipient <= to_unsigned(rc, 2);
            b_request <= to_unsigned(rq, 8);
            if di = 1 then dir_in <= '1'; else dir_in <= '0'; end if;
            if rq = 0 or rq = 6 or rq = 8 or rq = 10 or rq = 12 then
              w_length <= to_unsigned(8, 16);
            else
              w_length <= to_unsigned(0, 16);
            end if;
            check_now("sweep");
          end loop;
        end loop;
      end loop;
    end loop;
    report "  phase 1 exhaustive  : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors  (288 verdicts)";

    -- ---- PHASE 2 : the bounds ----
    dev_state <= "01"; recipient <= "00"; b_request <= to_unsigned(5, 8);
    dir_in <= '0'; w_index <= (others => '0'); w_length <= (others => '0');
    w_value <= to_unsigned(127, 16); check_now("addr 127 legal");
    w_value <= to_unsigned(128, 16); check_now("addr 128 illegal");

    dev_state <= "10"; b_request <= to_unsigned(9, 8);
    w_value <= to_unsigned(0, 16); check_now("config 0 legal");
    w_value <= to_unsigned(2, 16); check_now("config 2 legal");
    w_value <= to_unsigned(3, 16); check_now("config 3 illegal");
    w_value <= to_unsigned(1, 16); w_length <= to_unsigned(8, 16);
    check_now("set-config with length");
    w_length <= to_unsigned(0, 16);

    dev_state <= "10"; recipient <= "01"; b_request <= to_unsigned(10, 8);
    dir_in <= '1'; w_length <= to_unsigned(1, 16);
    w_index <= to_unsigned(2, 16); check_now("iface 2 exists");
    w_index <= to_unsigned(3, 16); check_now("iface 3 does not");
    dev_state <= "01"; b_request <= to_unsigned(0, 8);
    w_length <= to_unsigned(2, 16);
    w_index <= to_unsigned(0, 16); check_now("iface 0 in address state");
    w_index <= to_unsigned(1, 16); check_now("iface 1 in address state");

    dev_state <= "10"; recipient <= "10"; b_request <= to_unsigned(0, 8);
    dir_in <= '1'; w_length <= to_unsigned(2, 16);
    w_index <= to_unsigned(4, 16); check_now("ep 4 exists");
    w_index <= to_unsigned(5, 16); check_now("ep 5 does not");

    recipient <= "01"; req_type <= "01"; b_request <= to_unsigned(10, 8);
    dir_in <= '1'; w_index <= (others => '0'); w_length <= to_unsigned(1, 16);
    check_now("class request passes through");
    req_type <= "00";

    report "  phase 2 bounds      : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors";
    report "  ---- DIRECTED-ONLY  : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors ----";

    -- ---- PHASE 3 : random ----
    in_random := true;
    for i in 0 to 19999 loop
      dev_state <= to_unsigned(rnd(3), 2);
      recipient <= to_unsigned(rnd(3), 2);
      if rnd(100) < 85 then req_type <= "00";
      else                  req_type <= to_unsigned(rnd(3), 2); end if;
      if rnd(100) < 80 then b_request <= to_unsigned(rnd(13), 8);
      else                  b_request <= to_unsigned(rnd(256), 8); end if;
      if rnd(2) = 1 then dir_in <= '1'; else dir_in <= '0'; end if;
      if rnd(100) < 60 then w_value <= to_unsigned(rnd(132), 16);
      else                  w_value <= to_unsigned(rnd(65536), 16); end if;
      if rnd(100) < 70 then w_index <= to_unsigned(rnd(8), 16);
      else                  w_index <= to_unsigned(rnd(65536), 16); end if;
      if rnd(100) < 50 then w_length <= (others => '0');
      else                  w_length <= to_unsigned(rnd(64), 16); end if;
      n_configs    <= to_unsigned(rnd(4) + 1, 8);
      n_interfaces <= to_unsigned(rnd(4) + 1, 8);
      n_endpoints  <= to_unsigned(rnd(8), 4);
      check_now("random");
    end loop;
    in_random := false;

    report "  verdicts reached (all phases)";
    report "    must handle ............ " & integer'image(m_handle);
    report "    must stall ............. " & integer'image(m_stall);
    report "      undefined in state ... " & integer'image(m_state);
    report "      wrong direction ...... " & integer'image(m_dirn);
    report "      wrong recipient ...... " & integer'image(m_recip);
    report "      index out of range ... " & integer'image(m_index);
    report "      value out of range ... " & integer'image(m_value);
    report "      length wrong ......... " & integer'image(m_length);
    report "      not a defined request  " & integer'image(m_unknown);
    report "    not a standard request . " & integer'image(m_notstd);
    report "  directed checks ........ " & integer'image(chk_dir);
    report "  random checks .......... " & integer'image(chk_rnd);
    report "  TOTAL checks ........... " & integer'image(chk_dir + chk_rnd);
    report "  ERRORS ................. " & integer'image(errs);
    if errs = 0 then report "  PASS"; else report "  FAIL" severity failure; end if;
    wait;
  end process stim;

end architecture sim;

12. What A Compliance Test Actually Checks

Worth knowing so that a review can be honest about what it is preparing for. Categories, and which of them an RTL review can touch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    CATEGORY                    RTL REVIEW CAN  SETTLED BY
    --------------------------  --------------  ---------------------------
    descriptor correctness      yes -- the      a descriptor parser, and
                                content is      reading the class document
                                data we author
    Chapter 9 behaviour         yes -- this     a legality table, and the
                                chapter         sweep above
    device state transitions    yes             a state-machine review and
                                                an exhaustive sweep
    endpoint legality           yes             descriptors vs what the
                                                controller implements
    transfer behaviour          partly          simulation for the logic;
                                                a host for the scheduling
    class-specific behaviour    partly          the class document, which
                                                is a different document
    electrical and timing       NO              instruments, on a board
    interoperability            NO              many hosts, over time

13. The Checklist

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  SCOPE
       [ ] which specification, which version, which class documents
       [ ] which speeds are claimed
       [ ] every claim in the datasheet maps to a requirement somewhere

    2  DESCRIPTORS
       [ ] every descriptor parses, and parses to what was intended
       [ ] lengths and totals are consistent with the contents
       [ ] every endpoint described exists in the controller, and every
           endpoint in the controller is described
       [ ] maximum packet sizes are legal for the speed claimed
       [ ] string indices that are referenced exist

    3  CHAPTER 9 BEHAVIOUR
       [ ] a state x request table exists, in writing
       [ ] every "undefined" cell is a DECISION to reject, not a default
       [ ] field bounds enforce the SPECIFIED range, not the field width
       [ ] bounds are checked for being too strict as well as too loose
       [ ] the data direction agrees with the request
       [ ] a class or vendor request is not rejected by the standard layer

    4  DEVICE STATES
       [ ] every transition in the specification exists in the design
       [ ] a bus reset returns the device to Default from every state
       [ ] SET_ADDRESS to zero and SET_CONFIGURATION to zero both work
       [ ] behaviour after configuration is tested, not only enumeration

    5  EVIDENCE
       [ ] the legality table is swept exhaustively, with a derived
           denominator and named exclusions
       [ ] every verdict class is reached at least once
       [ ] the reference table is written independently of the design
       [ ] a reason code accompanies every rejection

    6  HONESTY
       [ ] the review says which categories it CANNOT settle
       [ ] electrical and interoperability are named as out of scope
       [ ] lab time is booked
       [ ] nobody has written "compliant" where "conformant to our
           reading" is meant

14. Exercises

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  THE TABLE
       Extend the state x request table to include the Suspended state.
       Which rows change? Which requests may a suspended device be
       required to answer, and what has to be true for it to do so?

    2  UNDEFINED
       Pick three "undefined" cells and write, for each, what a device
       that answered would have to return, and why no such value exists.

    3  TOO STRICT
       Find a second bounds check in the specimen that could be made
       wrong in the too-strict direction, and write the host behaviour it
       would break.

    4  VERILOG
       Add descriptor-type legality to GET_DESCRIPTOR: which descriptor
       types may be requested in which state, and with what index.

    5  SYSTEMVERILOG
       Implement it with the enum extended. Does the compiler now catch
       anything it did not?

    6  VHDL
       Implement it, and say what the compiler catches that the other two
       do not.

    7  DENOMINATOR
       Derive the denominator for the extended table. Is it still
       exhaustible? At what point does it stop being, and what replaces
       exhaustion?

    8  EVIDENCE CLASSIFICATION
       For each of these, say which of the four things called compliance
       in section 1 it settles: a passing simulation; a device that
       enumerates on three laptops; a descriptor parser reporting clean;
       an eye diagram; a USB-IF test event result.

    9  THE HONEST PARAGRAPH
       Write the paragraph a review report should contain about what the
       review did not establish. Four items minimum.

15. The Interview Answer

"Our device works on every machine we have tried. What is left to worry about?"

That sentence describes interoperability with the hosts you own, which is one of four different things people call compliance, and it is the weakest of them as evidence.

Working on every machine in the building means every host stack you tried sends a subset of the defined requests, in an order you happen to handle, and your device answers them. Different stacks send different subsets. The requests your device gets wrong are, by construction, the ones nobody on your desk sends — which is exactly why functional testing cannot close this gap. Enumeration in particular is one path through the state machine, and conformance is a property of the whole machine: the requirements that apply after configuration are not exercised by plugging it in.

So the thing I would want is a state-by-request table, in writing, checked exhaustively. It is small — three device states, eleven standard requests, three recipients, both directions is 288 verdicts — and it is one of the rare things in hardware you can check completely. The cells that matter most are the ones the specification calls undefined, because "undefined" gets read as "don't care" and it is not: a device that answers GET_CONFIGURATION before it has an address returns a value that cannot be true.

I would also check the bounds in both directions. Too loose is the obvious one — accepting an address above 127 because the field is sixteen bits wide. Too strict is the one review misses, because rejecting things feels safe: SET_CONFIGURATION with value zero is legal and means unconfigure, and a device that treats it as an error breaks a host that is deliberately tearing down.

And then the honest part. None of that touches electrical compliance — eye diagrams, inrush, droop — and none of it touches interoperability with a stack that does something the specification permits it not to do. Those are settled by instruments and by time, not by simulation. The practical consequence is a schedule one: book lab time early and treat the first electrical run as a milestone, because the RTL can be finished and the product can still be six weeks from a logo.

The word I would be careful with is "compliant". What a review like this produces is conformance to our reading of the document, which is worth a great deal and is not the same thing as a certification result.

16. What Carries Forward

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE DISTINCTION
    o  four different things are called compliance: team review, functional
       verification, specification conformance, official certification --
       and each proves only what it proves
    o  "works on every machine we tried" is interoperability with the hosts
       you own, and it is the weakest evidence of the four
    o  enumeration is one PATH; conformance is a property of the whole
       state machine

    THE ITEMS
    o  a state x request table belongs in writing, and every UNDEFINED cell
       is a decision to reject rather than a don't-care
    o  field bounds enforce the SPECIFIED range, not the field width -- and
       a bound that is too strict is a conformance defect too
    o  a class request is not the standard layer's to reject
    o  a reason code beside every rejection converts a lab day into ten
       minutes

    THE EVIDENCE
    o  a legality table is exhaustible: 288 verdicts, derived denominator,
       named exclusions, every verdict class reached
    o  a conformance defect is intrinsically LOW-YIELD to test and
       HIGH-CONSEQUENCE to ship, so mutation scores must not be used to
       rank findings -- only to confirm each cell is reached
    o  the language gradient is real for transcribed tables: a silent
       default, a warning, a compile error -- and none of the three helps
       with a row that is present and wrong

    THE HONESTY
    o  electrical and interoperability cannot be settled here, at all
    o  the review report says what it did not establish
    o  "conformant to our reading" is the true claim; "compliant" is not

The next chapter moves outward again: the controller is correct and conformant, and now it has to live inside a chip with other people's clocks, other people's resets, and firmware that was written against a datasheet.

Continue learning

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.