Skip to content
VLSI Mentor

USB · Module 20

USB 3.x Packets

Every SuperSpeed packet carries two independent CRCs, and that is not redundancy: a corrupt header is a link-layer problem while corrupt data is a protocol-layer one, so the header CRC must gate the type decode.

Chapter 20.3 got the link trained. Symbols are locked, the clock is recovered, both directions agree. The link can now carry packets.

This chapter is about what those packets are — and about one structural decision inside them that almost every first-draft receiver gets wrong.

1. Four Packet Types, and Only One Carries Data

SuperSpeed defines exactly four kinds of packet. The list is short and worth memorising, because every SuperSpeed trace you will ever read is made of these and nothing else.

TypeNameCarries data?Consumed by
0x00LMP — Link Management Packetnothe link layer at the other end of this link
0x04TP — Transaction Packetnothe protocol layer — an endpoint
0x08DP — Data Packetyesthe protocol layer — an endpoint
0x0CITP — Isochronous Timestamp Packetno (a timestamp)every device, broadcast

Three of the four carry no payload at all. ACK, NRDY, ERDY, STALL, the credit updates from Chapter 20.1 — all of those are Transaction Packets, distinguished by a subtype field inside the header rather than by being different kinds of packet.

2. Every Packet Begins With a 14-Byte Header

Whatever the type, the packet opens the same way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  [ HEADER : 14 bytes ][ CRC-16 ][  PAYLOAD  ][ CRC-32 ]
   \_______ one CRC domain ______/  \__ a second CRC domain __/
        type, route, sequence          only a Data Packet
        number, subtype, flags         has this part at all

Fourteen bytes of header, then two bytes of CRC-16 covering those fourteen bytes and nothing else. A Data Packet then appends its payload and a separate CRC-32 covering the payload and nothing else.

A SuperSpeed packet, and its two independent CRC domains

A SuperSpeed packet shown as four fields: a 14-byte header covered by a CRC-16, and an optional payload covered by a separate CRC-32. The header CRC fails into the link layer; the payload CRC fails into the protocol layer.Header14 bytes: type, route,sequenceCRC-16the header alonePayloadData Packets onlyCRC-32the payload aloneLink layerretry the whole packetProtocol layerNAK this one transfercoverscoverson failon fail12
The header CRC and the payload CRC protect different things and fail into different layers. That separation is the subject of this chapter.

3. Two CRCs Is Not Redundancy

The obvious reading of two CRCs is "belt and braces" — extra protection for the same data. It is not that at all. The two CRCs cover disjoint byte ranges, and a failure in each one means something categorically different.

Header CRC failsPayload CRC fails
What is damagedthe packet's identitythe packet's contents
Do we know the type?noyes
Do we know the endpoint?noyes
Do we know the sequence number?noyes
Who can act on itonly the link layerthe protocol layer
Recoveryresend the whole packetresend this transfer's data
Retry is likely to helpyes — transient bit erroryes

Read the "no" column again. When the header CRC fails, there is nothing to report upward. You cannot tell the protocol layer "endpoint 3's data was corrupt", because the field that said endpoint 3 is inside the bytes the CRC just declared untrustworthy. The receiver's honest statement is "a packet arrived and I cannot say what it was."

That is a link-layer event, and the link layer's answer is LBAD — tell the far end the packet was bad and let it resend the whole thing. Nothing above the link layer ever hears about it.

When only the payload CRC fails, the situation is completely different and much better. The header was intact, so the receiver knows the type, the endpoint, the direction and the sequence number. Exactly one transfer's data is bad, and the protocol layer can ask that one endpoint for that one packet again.

4. The Consequence: The CRC Is Checked Before the Type Is Read

Here is the decision the whole chapter turns on.

A receiver has the header bytes and the CRC result available at the same moment. It can write its decode either way round:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG -- decode first, validate afterwards
always_comb begin
  case (hdr_type)                 // reads the type field unconditionally
    T_LMP: pkt_type = PKT_LMP;
    T_TP:  pkt_type = PKT_TP;
    ...
  endcase
  if (!hdr_crc_ok) flag_an_error(); // ... and *then* notices it was garbage
end

versus:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RIGHT -- the CRC gates the decode
always_comb begin
  if (!pkt_valid)       pkt_type = PKT_NONE;
  else if (!hdr_crc_ok) pkt_type = PKT_UNKNOWN;   // stop here. Read nothing.
  else case (hdr_type)
    ...
  endcase
end

The first version works perfectly until the first corrupted header, and then routes a packet on the strength of bits that are known to be wrong.

This is not a theoretical worry. The header's routing field is what selects a device behind a hub, and its endpoint field selects a buffer inside that device. A single flipped bit in a corrupted header can name a different, innocent endpoint — one whose data stream now receives a fragment belonging to somebody else. The CRC caught the corruption. The decode order threw the catch away.

So the design in this chapter reports a distinct value, PKT_UNKNOWN, meaning "a packet arrived; its type is not knowable." That value is not a diagnostic convenience. It is the only truthful answer, and making it a first-class output is what stops the rest of the receiver from inventing one.

5. Reserved Types Are a Third Case, Not a Fifth Type

The header's Type field is five bits — 32 encodings, of which four are defined. The other 28 are reserved, and a packet carrying one of them is a third distinct situation:

header CRCtype fieldwhat to do
Known typegoodone of the fourdecode and route it
Reserved typegoodone of the other 28discard, no link retry
Corrupt headerbadunreadablediscard, link retry

The middle row is the one that gets conflated with the bottom row, and the difference matters: a reserved type must not trigger a link retry. The CRC passed, so the bytes arrived exactly as the transmitter sent them. Asking for them again returns the same reserved encoding, and the link spends its error budget on a packet that was never damaged. What a reserved type actually means is either a newer specification revision or a broken transmitter — and in both cases the correct behaviour is to drop the packet silently and count it.

This is forward compatibility working as designed: a device built to an older revision meets a packet type it has never heard of, ignores it, and keeps the link up.

The decode, in the only order that is safe

A decode pipeline. A packet arrives, the header CRC is checked first. If the CRC is bad the type is reported UNKNOWN and a link retry is requested. If the CRC is good the type field is decoded, and either routes the packet or, for one of the 28 undefined encodings, reports RESERVED and discards it without a retry.Packet arrivesheader, CRC resultsHeader CRCchecked FIRSTType decodeonly now is it readRoutelink or protocolUNKNOWNtype not knowableRESERVEDgood CRC, no meaningLink retryLBAD to the partnerDiscardcount it, no retryokbadundefined12
The CRC result gates the type field. UNKNOWN and RESERVED are separate outcomes with separate recoveries: only the first one justifies asking the far end to resend.

6. Where Each Packet Goes

Putting sections 1, 3 and 5 together gives the complete routing table the decoder implements:

Decoded asRouteAcknowledged?Notes
PKT_NONEnone—no packet this cycle
PKT_LMPlinklink-levelnever reaches an endpoint
PKT_TPprotocolyesACK, NRDY, ERDY, credits
PKT_DPprotocolyesrouted even if its payload CRC failed
PKT_ITPprotocolnobroadcast timestamp
PKT_RSVDdiscard—no link retry
PKT_UNKNOWNdiscard—link retry

Two rows there are easy to get wrong.

PKT_DP is routed even when its payload CRC failed. The instinct is to discard a packet with a bad CRC, but discarding it destroys the only copy of the information the protocol layer needs to recover: which endpoint and which sequence number to ask again. The header was good. Use it. The packet is delivered with payload_error asserted alongside it — delivered and flagged, not dropped.

PKT_ITP is never acknowledged. An Isochronous Timestamp Packet is broadcast to everything on the bus and carries the host's notion of time. Acknowledging it would be pointless: by the time an ACK travelled back, the timestamp it confirmed would already be stale, and with many devices on the bus the ACKs would flood the very bus whose timing the ITP exists to distribute. It is fire-and-forget by design.

7. What We Are Building

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  usb3_packet_decode

  inputs                          outputs
  ------                          -------
  pkt_valid                       pkt_type         (7 outcomes)
  hdr_type        [4:0]           route            (4 outcomes)
  hdr_crc_ok                      hdr_error
  payload_present                 payload_error
  payload_crc_ok                  link_retry_req
                                  protocol_nak_req
                                  ack_expected

                                  hdr_errors       [31:0]
                                  payload_errors   [31:0]
                                  reserved_types   [31:0]
                                  delivered        [31:0]

The whole decode is combinational; only the four diagnostic counters are registered. That is deliberate — a receiver that needs a pipeline stage to decide where a packet goes has to buffer the packet while it decides, and the header is designed to be classifiable in the cycle it arrives.

The decision surface is small enough to enumerate completely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  2 (pkt_valid)   x  32 (hdr_type)  x  2 (hdr_crc_ok)
                  x   2 (payload_present)  x  2 (payload_crc_ok)

                  =  512 points, ALL of them reachable

512 points is the entire input space, including all 28 reserved type encodings individually rather than one representative sample of them. The testbenches sweep every point on all three implementations.

8. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb3_packet_decode -- four packet types, two independent CRCs, and why a
// corrupt header is a different kind of problem from corrupt data.
//
// A SuperSpeed packet begins with a 14-byte HEADER carrying its type and its
// routing, followed by a 2-byte CRC over that header alone. A Data Packet
// then carries a PAYLOAD with its own, separate CRC.
//
//     [ header : 14 bytes ][ header CRC-16 ][ payload ][ payload CRC-32 ]
//      \______ protected together ______/    \___ protected separately __/
//
// THE STRUCTURAL DECISION
//
// Those two CRCs are not redundancy. They protect two things that fail in
// different ways and are recovered by different layers:
//
//   HEADER CRC BAD    The type and routing are UNKNOWN. The link layer
//                     cannot even tell what kind of packet this was, let
//                     alone which endpoint it belonged to. Nothing above
//                     the link layer can be told about it, because there is
//                     nothing to tell them. Recovery is a LINK-level retry.
//
//   PAYLOAD CRC BAD   The header was good, so the type, the endpoint and
//                     the sequence number are all known and trustworthy.
//                     Exactly one endpoint's data is bad, and the protocol
//                     layer can ask for that one packet again.
//
// THE CONSEQUENCE, AND IT IS THE POINT OF THE MODULE
//
// When the header CRC fails, the type field MUST NOT BE DECODED. The bits
// are there and they will parse into something -- but they are bits a CRC
// has just declared untrustworthy, and acting on them routes a packet
// somewhere on the strength of data known to be corrupt.
//
// A design that decodes the type first and checks the CRC afterwards works
// perfectly until the first corrupted header, and then delivers garbage to
// whichever endpoint the damaged bits happened to name.
//
// RESERVED TYPES ARE NOT A FIFTH KIND
//
// The 5-bit type field has 32 encodings and four are defined. The other 28
// are reserved, and a packet carrying one has a VALID CRC and an
// UNINTERPRETABLE type -- which is a third case, distinct from both a CRC
// failure and a known type, and it is discarded without a link retry
// because retrying will produce the same reserved value again.
module usb3_packet_decode (
  input  wire        clk,
  input  wire        rst_n,

  input  wire        pkt_valid,
  input  wire [4:0]  hdr_type,        // the header's Type field
  input  wire        hdr_crc_ok,      // the 16-bit header CRC checked out
  input  wire        payload_present, // a Data Packet carries one
  input  wire        payload_crc_ok,  // the 32-bit payload CRC checked out

  output wire [2:0]  pkt_type,
  output wire [1:0]  route,
  output wire        hdr_error,
  output wire        payload_error,
  output wire        link_retry_req,  // LINK layer: resend the whole packet
  output wire        protocol_nak_req,// PROTOCOL layer: resend this data
  output wire        ack_expected,    // ITP is never acknowledged

  output reg  [31:0] hdr_errors,
  output reg  [31:0] payload_errors,
  output reg  [31:0] reserved_types,
  output reg  [31:0] delivered
);
  // The four defined Type encodings. Everything else is reserved.
  localparam [4:0] T_LMP = 5'h00,   // Link Management -- stays at the link
                   T_TP  = 5'h04,   // Transaction Packet
                   T_DP  = 5'h08,   // Data Packet -- the only one with a payload
                   T_ITP = 5'h0C;   // Isochronous Timestamp -- broadcast

  localparam [2:0] P_NONE = 3'd0,   // no packet this cycle
                   P_LMP  = 3'd1,
                   P_TP   = 3'd2,
                   P_DP   = 3'd3,
                   P_ITP  = 3'd4,
                   P_RSVD = 3'd5,   // valid CRC, undefined type
                   P_UNKNOWN = 3'd6;// the CRC failed: the type is NOT known

  localparam [1:0] R_NONE     = 2'd0,
                   R_LINK     = 2'd1,   // the link layer consumes it
                   R_PROTOCOL = 2'd2,   // it goes up to the endpoint
                   R_DISCARD  = 2'd3;

  // THE DECODE, AND ITS ORDER. The CRC is consulted BEFORE the type field,
  // not after. With a bad header CRC the type is reported as P_UNKNOWN --
  // not as whatever the corrupt bits happen to spell.
  assign pkt_type = !pkt_valid   ? P_NONE
                  : !hdr_crc_ok  ? P_UNKNOWN
                  : (hdr_type == T_LMP) ? P_LMP
                  : (hdr_type == T_TP)  ? P_TP
                  : (hdr_type == T_DP)  ? P_DP
                  : (hdr_type == T_ITP) ? P_ITP
                                        : P_RSVD;

  // A Data Packet whose payload failed its own CRC still has a GOOD header,
  // so it is still routed: the protocol layer needs to know which endpoint
  // to ask again, and that information is in the header it can trust.
  assign route = (pkt_type == P_NONE)    ? R_NONE
               : (pkt_type == P_UNKNOWN) ? R_DISCARD
               : (pkt_type == P_RSVD)    ? R_DISCARD
               : (pkt_type == P_LMP)     ? R_LINK
                                         : R_PROTOCOL;

  assign hdr_error     = pkt_valid && !hdr_crc_ok;
  // A payload error is only meaningful on a Data Packet with a good header.
  // Reporting one after a header failure would be claiming to know that the
  // payload was bad -- which requires knowing there WAS a payload, which
  // requires the header.
  assign payload_error = pkt_valid && hdr_crc_ok
                      && (pkt_type == P_DP) && payload_present
                      && !payload_crc_ok;

  // TWO DIFFERENT RECOVERIES, and they are mutually exclusive by
  // construction: one of them needs a header the other one does not have.
  assign link_retry_req   = hdr_error;
  assign protocol_nak_req = payload_error;

  // An Isochronous Timestamp Packet is broadcast and never acknowledged --
  // an ACK would be pointless for a timestamp, which is stale the moment it
  // is questioned. LMPs are link-level and acknowledged differently.
  assign ack_expected = (pkt_type == P_TP) || (pkt_type == P_DP);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      hdr_errors     <= 32'd0;
      payload_errors <= 32'd0;
      reserved_types <= 32'd0;
      delivered      <= 32'd0;
    end else if (pkt_valid) begin
      if (hdr_error)              hdr_errors     <= hdr_errors + 32'd1;
      if (payload_error)          payload_errors <= payload_errors + 32'd1;
      if (pkt_type == P_RSVD)     reserved_types <= reserved_types + 32'd1;
      if (route != R_DISCARD && route != R_NONE)
                                  delivered      <= delivered + 32'd1;
    end
  end
endmodule

Three things in that listing are worth pausing on.

The order of the ternary chain is the design. !hdr_crc_ok ? P_UNKNOWN sits above every comparison against hdr_type, so the type field is not read at all on a CRC failure. Swap those two lines and the module still compiles, still passes every directed test that uses good headers, and is wrong.

route is computed from pkt_type, not from the raw inputs. That is not a stylistic choice. Deriving the route from hdr_type directly would reintroduce exactly the bug the decode order was written to avoid — there would be a second, independent reader of the untrusted field. By making pkt_type the single interpretation of the header, every consumer inherits the CRC gate for free.

payload_error carries its own hdr_crc_ok term even though pkt_type == P_DP already implies the CRC passed. The redundancy is intentional: it makes the property "a payload error is never claimed without a trustworthy header" true locally, readable in the one expression, rather than true only as a consequence of how pkt_type happens to be computed three assignments earlier.

9. SystemVerilog Implementation

The SystemVerilog build names the outcomes with enumerations. That is not cosmetic here: PKT_UNKNOWN becomes a value a downstream case statement must handle, and a receiver stage that forgets the corrupt-header case gets a lint warning instead of silently falling through a default.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb3_packet_decode -- four packet types, two independent CRCs, and why a
// corrupt header is a different kind of problem from corrupt data.
//
// A SuperSpeed packet begins with a 14-byte HEADER carrying its type and its
// routing, followed by a 2-byte CRC over that header alone. A Data Packet
// then carries a PAYLOAD with its own, separate CRC.
//
//     [ header : 14 bytes ][ header CRC-16 ][ payload ][ payload CRC-32 ]
//      \______ protected together ______/    \___ protected separately __/
//
// The SystemVerilog build names the two classifications with enums, which is
// what makes the central rule visible in the waveform viewer: when the header
// CRC fails the type reads PKT_UNKNOWN, not "the type the corrupt bits spell".
package usb3_packet_pkg;
  // The four defined Type encodings. Everything else is reserved.
  typedef enum logic [4:0] {
    T_LMP = 5'h00,   // Link Management -- stays at the link layer
    T_TP  = 5'h04,   // Transaction Packet
    T_DP  = 5'h08,   // Data Packet -- the only one with a payload
    T_ITP = 5'h0C    // Isochronous Timestamp -- broadcast, never acknowledged
  } hdr_type_e;

  typedef enum logic [2:0] {
    PKT_NONE    = 3'd0,  // no packet this cycle
    PKT_LMP     = 3'd1,
    PKT_TP      = 3'd2,
    PKT_DP      = 3'd3,
    PKT_ITP     = 3'd4,
    PKT_RSVD    = 3'd5,  // valid CRC, undefined type encoding
    PKT_UNKNOWN = 3'd6   // the header CRC FAILED: the type is not known
  } pkt_type_e;

  typedef enum logic [1:0] {
    ROUTE_NONE     = 2'd0,
    ROUTE_LINK     = 2'd1,  // the link layer consumes it
    ROUTE_PROTOCOL = 2'd2,  // it goes up to the endpoint
    ROUTE_DISCARD  = 2'd3
  } route_e;
endpackage

// THE STRUCTURAL DECISION
//
// The two CRCs are not redundancy. They protect two things that fail in
// different ways and are recovered by different layers:
//
//   HEADER CRC BAD    The type and routing are UNKNOWN. The link layer cannot
//                     tell what kind of packet this was, let alone which
//                     endpoint it belonged to. Nothing above the link layer
//                     can be told about it, because there is nothing to tell
//                     them. Recovery is a LINK-level retry.
//
//   PAYLOAD CRC BAD   The header was good, so the type, the endpoint and the
//                     sequence number are all known and trustworthy. Exactly
//                     one endpoint's data is bad and the protocol layer can
//                     ask for that one packet again.
//
// THE CONSEQUENCE, AND IT IS THE POINT OF THE MODULE
//
// When the header CRC fails, the type field MUST NOT BE DECODED. The bits are
// there and they will parse into something -- but they are bits a CRC has
// just declared untrustworthy, and acting on them routes a packet somewhere
// on the strength of data known to be corrupt.
//
// RESERVED TYPES ARE NOT A FIFTH KIND
//
// The 5-bit type field has 32 encodings and four are defined. The other 28 are
// reserved, and a packet carrying one has a VALID CRC and an UNINTERPRETABLE
// type -- a third case, distinct from both a CRC failure and a known type. It
// is discarded WITHOUT a link retry, because retrying will produce the same
// reserved value again.
module usb3_packet_decode
  import usb3_packet_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        pkt_valid,
  input  logic [4:0]  hdr_type,        // the header's Type field
  input  logic        hdr_crc_ok,      // the 16-bit header CRC checked out
  input  logic        payload_present, // a Data Packet carries one
  input  logic        payload_crc_ok,  // the 32-bit payload CRC checked out

  output pkt_type_e   pkt_type,
  output route_e      route,
  output logic        hdr_error,
  output logic        payload_error,
  output logic        link_retry_req,  // LINK layer: resend the whole packet
  output logic        protocol_nak_req,// PROTOCOL layer: resend this data
  output logic        ack_expected,    // ITP is never acknowledged

  output logic [31:0] hdr_errors,
  output logic [31:0] payload_errors,
  output logic [31:0] reserved_types,
  output logic [31:0] delivered
);
  pkt_type_e pkt_type_c;
  route_e    route_c;

  // THE DECODE, AND ITS ORDER. The CRC is consulted BEFORE the type field,
  // never after. With a bad header CRC the type is PKT_UNKNOWN.
  always_comb begin
    if (!pkt_valid)      pkt_type_c = PKT_NONE;
    else if (!hdr_crc_ok) pkt_type_c = PKT_UNKNOWN;
    else begin
      case (hdr_type)
        T_LMP:   pkt_type_c = PKT_LMP;
        T_TP:    pkt_type_c = PKT_TP;
        T_DP:    pkt_type_c = PKT_DP;
        T_ITP:   pkt_type_c = PKT_ITP;
        default: pkt_type_c = PKT_RSVD;
      endcase
    end
  end

  // A Data Packet whose payload failed its own CRC still has a GOOD header,
  // so it is still routed: the protocol layer needs to know which endpoint to
  // ask again, and that information is in the header it can trust.
  always_comb begin
    case (pkt_type_c)
      PKT_NONE:               route_c = ROUTE_NONE;
      PKT_UNKNOWN, PKT_RSVD:  route_c = ROUTE_DISCARD;
      PKT_LMP:                route_c = ROUTE_LINK;
      default:                route_c = ROUTE_PROTOCOL;
    endcase
  end

  assign pkt_type = pkt_type_c;
  assign route    = route_c;

  assign hdr_error = pkt_valid && !hdr_crc_ok;
  // A payload error is only meaningful on a Data Packet with a good header.
  // Reporting one after a header failure would be claiming to know that the
  // payload was bad -- which requires knowing there WAS a payload, which
  // requires the header.
  assign payload_error = pkt_valid && hdr_crc_ok
                      && (pkt_type_c == PKT_DP) && payload_present
                      && !payload_crc_ok;

  // TWO DIFFERENT RECOVERIES, mutually exclusive by construction: one of them
  // needs a header the other one does not have.
  assign link_retry_req   = hdr_error;
  assign protocol_nak_req = payload_error;

  // An ITP is broadcast and never acknowledged -- an ACK for a timestamp is
  // pointless, since it is stale the moment it is questioned. LMPs are
  // link-level and acknowledged differently.
  assign ack_expected = (pkt_type_c == PKT_TP) || (pkt_type_c == PKT_DP);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      hdr_errors     <= '0;
      payload_errors <= '0;
      reserved_types <= '0;
      delivered      <= '0;
    end else if (pkt_valid) begin
      if (hdr_error)              hdr_errors     <= hdr_errors + 1;
      if (payload_error)          payload_errors <= payload_errors + 1;
      if (pkt_type_c == PKT_RSVD) reserved_types <= reserved_types + 1;
      if (route_c != ROUTE_DISCARD && route_c != ROUTE_NONE)
                                  delivered      <= delivered + 1;
    end
  end
endmodule

10. VHDL-2008 Implementation

VHDL takes the enumeration idea furthest. pkt_type_t is a genuine enumerated type, not an encoding, so PKT_UNKNOWN cannot be confused with an integer and a case over it must be exhaustive or the design will not analyse.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb3_packet_decode -- four packet types, two independent CRCs, and why a
-- corrupt header is a different kind of problem from corrupt data.
--
-- A SuperSpeed packet begins with a 14-byte HEADER carrying its type and its
-- routing, followed by a 2-byte CRC over that header alone. A Data Packet
-- then carries a PAYLOAD with its own, separate CRC.
--
--     [ header : 14 bytes ][ header CRC-16 ][ payload ][ payload CRC-32 ]
--      \______ protected together ______/    \___ protected separately __/
--
-- VHDL's strong enumeration types make the central rule structural rather
-- than conventional: PKT_UNKNOWN is a distinct value of pkt_type_t, so a
-- downstream case statement that forgets to handle "the header was corrupt"
-- fails to elaborate rather than falling through to a default.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb3_packet_pkg is
  -- Seven outcomes, and the last two are the interesting ones: PKT_RSVD is a
  -- GOOD header carrying an undefined encoding, PKT_UNKNOWN is a header whose
  -- CRC failed so no encoding can be read from it at all.
  type pkt_type_t is (PKT_NONE, PKT_LMP, PKT_TP, PKT_DP, PKT_ITP,
                      PKT_RSVD, PKT_UNKNOWN);
  type route_t    is (ROUTE_NONE, ROUTE_LINK, ROUTE_PROTOCOL, ROUTE_DISCARD);

  function pkt_code  (p : pkt_type_t) return std_logic_vector;
  function route_code(r : route_t)    return std_logic_vector;
end package;

package body usb3_packet_pkg is
  function pkt_code (p : pkt_type_t) return std_logic_vector is
  begin
    return std_logic_vector(to_unsigned(pkt_type_t'pos(p), 3));
  end function;
  function route_code (r : route_t) return std_logic_vector is
  begin
    return std_logic_vector(to_unsigned(route_t'pos(r), 2));
  end function;
end package body;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb3_packet_pkg.all;

-- THE STRUCTURAL DECISION
--
-- The two CRCs are not redundancy. They protect two things that fail in
-- different ways and are recovered by different layers:
--
--   HEADER CRC BAD    The type and routing are UNKNOWN. The link layer cannot
--                     tell what kind of packet this was, let alone which
--                     endpoint it belonged to. Recovery is a LINK-level retry.
--
--   PAYLOAD CRC BAD   The header was good, so the type, the endpoint and the
--                     sequence number are all trustworthy. Exactly one
--                     endpoint's data is bad and the protocol layer can ask
--                     for that one packet again.
--
-- THE CONSEQUENCE: when the header CRC fails, the type field MUST NOT BE
-- DECODED. The bits are there and they will parse into something -- but they
-- are bits a CRC has just declared untrustworthy.
--
-- RESERVED TYPES ARE NOT A FIFTH KIND. Four of the 32 encodings are defined;
-- a packet carrying one of the other 28 has a VALID CRC and an
-- UNINTERPRETABLE type, and is discarded WITHOUT a link retry, because
-- retrying will produce the same reserved value again.
entity usb3_packet_decode is
  port (
    clk              : in  std_logic;
    rst_n            : in  std_logic;

    pkt_valid        : in  std_logic;
    hdr_type         : in  std_logic_vector(4 downto 0);
    hdr_crc_ok       : in  std_logic;
    payload_present  : in  std_logic;
    payload_crc_ok   : in  std_logic;

    pkt_type         : out std_logic_vector(2 downto 0);
    route            : out std_logic_vector(1 downto 0);
    hdr_error        : out std_logic;
    payload_error    : out std_logic;
    link_retry_req   : out std_logic;
    protocol_nak_req : out std_logic;
    ack_expected     : out std_logic;

    hdr_errors       : out std_logic_vector(31 downto 0);
    payload_errors   : out std_logic_vector(31 downto 0);
    reserved_types   : out std_logic_vector(31 downto 0);
    delivered        : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of usb3_packet_decode is
  -- The four defined Type encodings. Everything else is reserved.
  constant T_LMP : std_logic_vector(4 downto 0) := "00000";  -- 0x00
  constant T_TP  : std_logic_vector(4 downto 0) := "00100";  -- 0x04
  constant T_DP  : std_logic_vector(4 downto 0) := "01000";  -- 0x08
  constant T_ITP : std_logic_vector(4 downto 0) := "01100";  -- 0x0C

  signal ptype  : pkt_type_t;
  signal rt     : route_t;
  signal he     : std_logic;
  signal pe     : std_logic;

  signal he_r, pe_r, rs_r, dl_r : unsigned(31 downto 0) := (others => '0');
begin
  -- THE DECODE, AND ITS ORDER. The CRC is consulted BEFORE the type field,
  -- never after. With a bad header CRC the type is PKT_UNKNOWN.
  decode : process (pkt_valid, hdr_crc_ok, hdr_type)
  begin
    if pkt_valid = '0' then
      ptype <= PKT_NONE;
    elsif hdr_crc_ok = '0' then
      ptype <= PKT_UNKNOWN;
    else
      case hdr_type is
        when T_LMP  => ptype <= PKT_LMP;
        when T_TP   => ptype <= PKT_TP;
        when T_DP   => ptype <= PKT_DP;
        when T_ITP  => ptype <= PKT_ITP;
        when others => ptype <= PKT_RSVD;
      end case;
    end if;
  end process;

  -- A Data Packet whose payload failed its own CRC still has a GOOD header,
  -- so it is still routed: the protocol layer needs to know which endpoint to
  -- ask again, and that is in the header it can trust.
  routing : process (ptype)
  begin
    case ptype is
      when PKT_NONE                => rt <= ROUTE_NONE;
      when PKT_UNKNOWN | PKT_RSVD  => rt <= ROUTE_DISCARD;
      when PKT_LMP                 => rt <= ROUTE_LINK;
      when others                  => rt <= ROUTE_PROTOCOL;
    end case;
  end process;

  he <= '1' when (pkt_valid = '1' and hdr_crc_ok = '0') else '0';
  -- A payload error is only meaningful on a Data Packet with a good header.
  -- Claiming one after a header failure would assert knowledge that there WAS
  -- a payload -- which requires the header.
  pe <= '1' when (pkt_valid = '1' and hdr_crc_ok = '1' and ptype = PKT_DP
                  and payload_present = '1' and payload_crc_ok = '0')
        else '0';

  pkt_type <= pkt_code(ptype);
  route    <= route_code(rt);

  hdr_error     <= he;
  payload_error <= pe;

  -- TWO DIFFERENT RECOVERIES, mutually exclusive by construction: one needs a
  -- header the other one does not have.
  link_retry_req   <= he;
  protocol_nak_req <= pe;

  -- An ITP is broadcast and never acknowledged -- an ACK for a timestamp is
  -- pointless, since it is stale the moment it is questioned.
  ack_expected <= '1' when (ptype = PKT_TP or ptype = PKT_DP) else '0';

  counters : process (clk, rst_n)
  begin
    if rst_n = '0' then
      he_r <= (others => '0');
      pe_r <= (others => '0');
      rs_r <= (others => '0');
      dl_r <= (others => '0');
    elsif rising_edge(clk) then
      if pkt_valid = '1' then
        if he = '1' then
          he_r <= he_r + 1;
        end if;
        if pe = '1' then
          pe_r <= pe_r + 1;
        end if;
        if ptype = PKT_RSVD then
          rs_r <= rs_r + 1;
        end if;
        if rt /= ROUTE_DISCARD and rt /= ROUTE_NONE then
          dl_r <= dl_r + 1;
        end if;
      end if;
    end if;
  end process;

  hdr_errors     <= std_logic_vector(he_r);
  payload_errors <= std_logic_vector(pe_r);
  reserved_types <= std_logic_vector(rs_r);
  delivered      <= std_logic_vector(dl_r);
end architecture;

The pkt_code and route_code functions exist only to present the enumerated values on std_logic_vector ports, so that the three implementations have identical port maps and can be compared observable-for-observable. Inside the architecture everything is strongly typed.

11. Seeing It Happen

Two packets, two different failures, two different recoveries — in adjacent cycles:

A header CRC failure and a payload CRC failure, side by side

usb3_packet_decode — two failures, two recoveries

10 cycles
A ten-cycle waveform. At cycle 1 a good Data Packet is routed to the protocol layer. At cycle 3 a packet arrives with hdr_crc_ok low: pkt_type reads UNKNOWN, route reads DISCARD and link_retry_req rises. At cycle 5 a packet arrives with hdr_crc_ok high and payload_crc_ok low: pkt_type reads DP, route reads PROTO and protocol_nak_req rises instead. At cycle 7 a Link Management Packet is routed to the link layer.header bad: LINK retryheader bad: LINK retrypayload bad: PROTOCOL NAKpayload bad: PROTOCOL NAKLMP stays at the linkLMP stays at the linkclkpkt_validhdr_crc_okpayload_crc_okpkt_typeNONEDPNONEUNKNOWNNONEDPNONELMPNONENONEroutenonePROTOnoneDISCARDnonePROTOnoneLINKnonenonelink_retry_reqprotocol_nak_reqt0t1t2t3t4t5t6t7t8t9
Cycle 3: the header CRC fails, the type reads UNKNOWN and the LINK layer is asked to retry. Cycle 5: the header is good and only the payload CRC fails, so the packet is still routed to the protocol layer, flagged for a NAK. The same input pin, two completely different outcomes.

Note cycle 5 carefully. route is PROTO, not DISCARD — the packet with the bad payload is delivered. That is the row from section 6 that looks like a bug and is not.

12. The Testbenches

Each testbench does three things: sweeps the complete 512-point input space, checks a handful of named directed scenarios, and then runs 40 000 randomised cycles against an independent reference model. The reference model is deliberately written in a different shape from the design — where the design uses a ternary chain the model uses a case, and vice versa — so that a single misunderstanding cannot be baked into both.

12.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_pk_v;
  reg clk=0, rst_n=0;
  reg pv=0, hco=0, pp=0, pco=0;
  reg [4:0] ht=0;
  wire [2:0] pkt_type;
  wire [1:0] route;
  wire hdr_error, payload_error, link_retry_req, protocol_nak_req;
  wire ack_expected;
  wire [31:0] hdr_errors, payload_errors, reserved_types, delivered;
  always #5 clk=~clk;

  usb3_packet_decode dut (
    .clk(clk), .rst_n(rst_n), .pkt_valid(pv), .hdr_type(ht),
    .hdr_crc_ok(hco), .payload_present(pp), .payload_crc_ok(pco),
    .pkt_type(pkt_type), .route(route), .hdr_error(hdr_error),
    .payload_error(payload_error), .link_retry_req(link_retry_req),
    .protocol_nak_req(protocol_nak_req), .ack_expected(ack_expected),
    .hdr_errors(hdr_errors), .payload_errors(payload_errors),
    .reserved_types(reserved_types), .delivered(delivered));

  localparam [2:0] P_NONE=0, P_LMP=1, P_TP=2, P_DP=3, P_ITP=4,
                   P_RSVD=5, P_UNK=6;
  localparam [1:0] R_NONE=0, R_LINK=1, R_PROTO=2, R_DISCARD=3;

  integer errors=0, i, t, c, q, r, v;
  integer n_exh=0;
  integer n_pt [0:6];
  integer n_rt [0:3];
  integer m_he, m_pe, m_rs, m_dl;

  task check(input cond, input [639:0] msg);
    begin if (!cond) begin errors=errors+1;
      if (errors <= 25)
        $display("  FAIL: %0s (pv=%b ht=%0h crc=%b pp=%b pcrc=%b | type=%0d route=%0d he=%b pe=%b, t=%0t)",
                 msg, pv, ht, hco, pp, pco, pkt_type, route, hdr_error,
                 payload_error, $time);
    end end
  endtask

  task check_comb;
    integer e_type, e_route;
    reg e_he, e_pe, e_ack;
    begin
      // The model decides the type with a case over the four defined
      // encodings rather than a chain of ternaries -- a different route.
      if (!pv)        e_type = P_NONE;
      else if (!hco)  e_type = P_UNK;
      else case (ht)
        5'h00:   e_type = P_LMP;
        5'h04:   e_type = P_TP;
        5'h08:   e_type = P_DP;
        5'h0C:   e_type = P_ITP;
        default: e_type = P_RSVD;
      endcase

      if (e_type == P_NONE)                            e_route = R_NONE;
      else if (e_type == P_UNK || e_type == P_RSVD)    e_route = R_DISCARD;
      else if (e_type == P_LMP)                        e_route = R_LINK;
      else                                             e_route = R_PROTO;

      e_he  = pv && !hco;
      e_pe  = pv && hco && (e_type == P_DP) && pp && !pco;
      e_ack = (e_type == P_TP) || (e_type == P_DP);

      check(pkt_type         === e_type[2:0],  "pkt_type matches the model");
      check(route            === e_route[1:0], "route matches the model");
      check(hdr_error        === e_he,         "hdr_error matches the model");
      check(payload_error    === e_pe,         "payload_error matches the model");
      check(link_retry_req   === e_he,         "link retry follows the header");
      check(protocol_nak_req === e_pe,         "protocol NAK follows the payload");
      check(ack_expected     === e_ack,        "only TP and DP are acknowledged");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE one that matters: with a bad header CRC the type is NOT
      //    decoded. Acting on the type field of a header a CRC has just
      //    declared corrupt routes a packet on untrustworthy bits.
      if (pv && !hco)
        check(pkt_type === P_UNK,
              "a corrupt header was decoded into a packet type anyway");
      // 2. And such a packet reaches nobody.
      if (pv && !hco)
        check(route === R_DISCARD,
              "a packet with a corrupt header was routed somewhere");
      // 3. The two recoveries are mutually exclusive: one needs a header
      //    the other does not have.
      check(!(link_retry_req && protocol_nak_req),
            "a link retry and a protocol NAK requested for one packet");
      // 4. A payload error is never claimed without a good header, because
      //    knowing there WAS a payload requires the header.
      check(!payload_error || hco,
            "a payload error claimed with no trustworthy header");
      // 5. Nothing is routed when no packet is present.
      if (!pv) check(route === R_NONE, "a route asserted with no packet");

      if (e_type >= 0 && e_type <= 6) n_pt[e_type] = n_pt[e_type] + 1;
      if (e_route >= 0 && e_route <= 3) n_rt[e_route] = n_rt[e_route] + 1;
    end
  endtask

  task step;
    begin
      #1;
      check_comb;
      if (pv) begin
        if (hdr_error)           m_he = m_he + 1;
        if (payload_error)       m_pe = m_pe + 1;
        if (pkt_type === P_RSVD) m_rs = m_rs + 1;
        if (route !== R_DISCARD && route !== R_NONE) m_dl = m_dl + 1;
      end
      @(posedge clk); #1;
      check(hdr_errors     === m_he[31:0], "hdr_errors matches the model");
      check(payload_errors === m_pe[31:0], "payload_errors matches the model");
      check(reserved_types === m_rs[31:0], "reserved_types matches the model");
      check(delivered      === m_dl[31:0], "delivered matches the model");
    end
  endtask

  task hard_reset;
    begin
      rst_n=0; pv=0; ht=0; hco=0; pp=0; pco=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      m_he=0; m_pe=0; m_rs=0; m_dl=0;
    end
  endtask

  initial begin
    for (i=0;i<7;i=i+1) n_pt[i]=0;
    for (i=0;i<4;i=i+1) n_rt[i]=0;
    hard_reset;
    check(pkt_type === P_NONE, "no packet, no type");
    check(route === R_NONE, "and no route");

    // ===== A. EXHAUSTIVE over the whole decode =====
    // 32 header-type encodings x header CRC ok/bad x payload present/absent
    // x payload CRC ok/bad x packet valid/not = 512 points, which is the
    // ENTIRE decision surface including all 28 reserved type encodings.
    for (v=0; v<2; v=v+1)
     for (t=0; t<32; t=t+1)
      for (c=0; c<2; c=c+1)
       for (q=0; q<2; q=q+1)
        for (r=0; r<2; r=r+1) begin
          pv=v[0]; ht=t[4:0]; hco=c[0]; pp=q[0]; pco=r[0];
          step;
          n_exh = n_exh + 1;
        end
    $display("  exhaustive packet-decode sweep: %0d of %0d points verified",
             n_exh, 2*32*2*2*2);

    // ===== B. directed: the four types and the two failures =====
    hard_reset;
    pv=1; ht=5'h00; hco=1; pp=0; pco=1; step;
    check(pkt_type === P_LMP, "type 0x00 is a Link Management Packet");
    check(route === R_LINK, "and it stays at the LINK layer");
    check(!ack_expected, "LMPs are not acknowledged this way");

    pv=1; ht=5'h04; hco=1; pp=0; pco=1; step;
    check(pkt_type === P_TP, "type 0x04 is a Transaction Packet");
    check(route === R_PROTO, "which goes up to the protocol layer");
    check(ack_expected, "and is acknowledged");

    pv=1; ht=5'h0C; hco=1; pp=0; pco=1; step;
    check(pkt_type === P_ITP, "type 0x0C is an Isochronous Timestamp Packet");
    check(route === R_PROTO, "which is delivered");
    check(!ack_expected,
          "but NEVER acknowledged: a timestamp is stale once questioned");

    // a Data Packet with a bad payload still routes
    pv=1; ht=5'h08; hco=1; pp=1; pco=0; step;
    check(pkt_type === P_DP, "type 0x08 is a Data Packet");
    check(payload_error, "its payload CRC failed");
    check(route === R_PROTO,
          "and it is STILL routed: the header says which endpoint to re-ask");
    check(protocol_nak_req, "with a PROTOCOL-level NAK");
    check(!link_retry_req, "and NOT a link retry");

    // a bad header is a different problem entirely
    pv=1; ht=5'h08; hco=0; pp=1; pco=1; step;
    check(pkt_type === P_UNK,
          "a corrupt header yields UNKNOWN, not the type its bits spell");
    check(route === R_DISCARD, "and the packet reaches nobody");
    check(link_retry_req, "recovery is a LINK retry");
    check(!protocol_nak_req, "and not a protocol NAK");
    check(!payload_error,
          "and no payload error is claimed: there is no trustworthy header");

    // a reserved type has a GOOD CRC and an uninterpretable type
    pv=1; ht=5'h11; hco=1; pp=0; pco=1; step;
    check(pkt_type === P_RSVD, "an undefined encoding is RESERVED");
    check(route === R_DISCARD, "and is discarded");
    check(!link_retry_req,
          "without a link retry: retrying returns the same reserved value");
    check(!hdr_error, "and it is not a header error -- the CRC was fine");

    // ===== C. randomised =====
    for (i=0;i<40000;i=i+1) begin
      pv=({$random}%8)!=0;
      // bias toward the four defined encodings so the reserved path is
      // exercised without swamping everything else
      if (({$random}%3)==0) ht = {$random}%32;
      else ht = (({$random}%4)*4);
      hco=({$random}%8)!=0; pp=({$random}%2); pco=({$random}%4)!=0;
      step;
    end

    for (i=0;i<7;i=i+1)
      check(n_pt[i] > 0, "every packet-type outcome was reached");
    for (i=0;i<4;i=i+1)
      check(n_rt[i] > 0, "every routing outcome was reached");

    $display("");
    $display("  REACH: exhaustive=%0d | types: none=%0d lmp=%0d tp=%0d dp=%0d itp=%0d rsvd=%0d unknown=%0d",
             n_exh, n_pt[0], n_pt[1], n_pt[2], n_pt[3], n_pt[4], n_pt[5],
             n_pt[6]);
    $display("  ROUTES: none=%0d link=%0d protocol=%0d discard=%0d | hdr-err=%0d payload-err=%0d reserved=%0d delivered=%0d",
             n_rt[0], n_rt[1], n_rt[2], n_rt[3], hdr_errors, payload_errors,
             reserved_types, delivered);
    $display("  [Verilog] usb3_packet_decode: %0d errors", errors);
    $display("  [Verilog] %0s", errors==0 ? "PASS" : "FAIL");
    $display("");
    $finish;
  end
endmodule

12.2 SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_pk_sv;
  import usb3_packet_pkg::*;

  logic clk=0, rst_n=0;
  logic pv=0, hco=0, pp=0, pco=0;
  logic [4:0] ht=0;
  pkt_type_e pkt_type;
  route_e    route;
  logic hdr_error, payload_error, link_retry_req, protocol_nak_req;
  logic ack_expected;
  logic [31:0] hdr_errors, payload_errors, reserved_types, delivered;
  always #5 clk=~clk;

  usb3_packet_decode dut (
    .clk, .rst_n, .pkt_valid(pv), .hdr_type(ht), .hdr_crc_ok(hco),
    .payload_present(pp), .payload_crc_ok(pco), .pkt_type, .route,
    .hdr_error, .payload_error, .link_retry_req, .protocol_nak_req,
    .ack_expected, .hdr_errors, .payload_errors, .reserved_types, .delivered);

  int errors=0, i, t, c, q, r, v;
  int n_exh=0;
  int n_pt [7];
  int n_rt [4];
  int m_he, m_pe, m_rs, m_dl;

  // Icarus will not call .name() on a net, so the enum outputs are copied
  // into variables of the same type before being printed.
  task automatic check(input bit cond, input string msg);
    pkt_type_e pt_v;
    route_e    rt_v;
    if (!cond) begin
      errors++;
      pt_v = pkt_type;
      rt_v = route;
      if (errors <= 25)
        $display("  FAIL: %0s (pv=%b ht=%0h crc=%b pp=%b pcrc=%b | type=%s route=%s, t=%0t)",
                 msg, pv, ht, hco, pp, pco, pt_v.name(), rt_v.name(), $time);
    end
  endtask

  task automatic check_comb;
    pkt_type_e e_type;
    route_e    e_route;
    bit e_he, e_pe, e_ack;
    begin
      // The model reaches the same answer by a different route: a priority
      // chain of comparisons rather than the design's case statement.
      if (!pv)                    e_type = PKT_NONE;
      else if (!hco)              e_type = PKT_UNKNOWN;
      else if (ht == 5'h00)       e_type = PKT_LMP;
      else if (ht == 5'h04)       e_type = PKT_TP;
      else if (ht == 5'h08)       e_type = PKT_DP;
      else if (ht == 5'h0C)       e_type = PKT_ITP;
      else                        e_type = PKT_RSVD;

      if (e_type == PKT_NONE)                                 e_route = ROUTE_NONE;
      else if (e_type == PKT_UNKNOWN || e_type == PKT_RSVD)   e_route = ROUTE_DISCARD;
      else if (e_type == PKT_LMP)                             e_route = ROUTE_LINK;
      else                                                    e_route = ROUTE_PROTOCOL;

      e_he  = pv && !hco;
      e_pe  = pv && hco && (e_type == PKT_DP) && pp && !pco;
      e_ack = (e_type == PKT_TP) || (e_type == PKT_DP);

      check(pkt_type         === e_type,  "pkt_type matches the model");
      check(route            === e_route, "route matches the model");
      check(hdr_error        === e_he,    "hdr_error matches the model");
      check(payload_error    === e_pe,    "payload_error matches the model");
      check(link_retry_req   === e_he,    "link retry follows the header");
      check(protocol_nak_req === e_pe,    "protocol NAK follows the payload");
      check(ack_expected     === e_ack,   "only TP and DP are acknowledged");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE one that matters: with a bad header CRC the type is NOT
      //    decoded. Acting on the type field of a header a CRC has just
      //    declared corrupt routes a packet on untrustworthy bits.
      if (pv && !hco)
        check(pkt_type === PKT_UNKNOWN,
              "a corrupt header was decoded into a packet type anyway");
      // 2. And such a packet reaches nobody.
      if (pv && !hco)
        check(route === ROUTE_DISCARD,
              "a packet with a corrupt header was routed somewhere");
      // 3. The two recoveries are mutually exclusive: one needs a header the
      //    other does not have.
      check(!(link_retry_req && protocol_nak_req),
            "a link retry and a protocol NAK requested for one packet");
      // 4. A payload error is never claimed without a good header.
      check(!payload_error || hco,
            "a payload error claimed with no trustworthy header");
      // 5. Nothing is routed when no packet is present.
      if (!pv) check(route === ROUTE_NONE, "a route asserted with no packet");

      n_pt[int'(e_type)]  = n_pt[int'(e_type)] + 1;
      n_rt[int'(e_route)] = n_rt[int'(e_route)] + 1;
    end
  endtask

  task automatic step;
    begin
      #1;
      check_comb;
      if (pv) begin
        if (hdr_error)              m_he++;
        if (payload_error)          m_pe++;
        if (pkt_type === PKT_RSVD)  m_rs++;
        if (route !== ROUTE_DISCARD && route !== ROUTE_NONE) m_dl++;
      end
      @(posedge clk); #1;
      check(hdr_errors     === 32'(m_he), "hdr_errors matches the model");
      check(payload_errors === 32'(m_pe), "payload_errors matches the model");
      check(reserved_types === 32'(m_rs), "reserved_types matches the model");
      check(delivered      === 32'(m_dl), "delivered matches the model");
    end
  endtask

  task automatic hard_reset;
    begin
      rst_n=0; pv=0; ht=0; hco=0; pp=0; pco=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      m_he=0; m_pe=0; m_rs=0; m_dl=0;
    end
  endtask

  initial begin
    foreach (n_pt[i]) n_pt[i]=0;
    foreach (n_rt[i]) n_rt[i]=0;
    hard_reset;
    check(pkt_type === PKT_NONE, "no packet, no type");
    check(route === ROUTE_NONE, "and no route");

    // ===== A. EXHAUSTIVE over the whole decode =====
    // 32 header-type encodings x header CRC ok/bad x payload present/absent
    // x payload CRC ok/bad x packet valid/not = 512 points -- the ENTIRE
    // decision surface, including all 28 reserved type encodings.
    for (v=0; v<2; v++)
     for (t=0; t<32; t++)
      for (c=0; c<2; c++)
       for (q=0; q<2; q++)
        for (r=0; r<2; r++) begin
          pv=v[0]; ht=t[4:0]; hco=c[0]; pp=q[0]; pco=r[0];
          step;
          n_exh++;
        end
    $display("  exhaustive packet-decode sweep: %0d of %0d points verified",
             n_exh, 2*32*2*2*2);

    // ===== B. directed: the four types and the two failures =====
    hard_reset;
    pv=1; ht=5'h00; hco=1; pp=0; pco=1; step;
    check(pkt_type === PKT_LMP, "type 0x00 is a Link Management Packet");
    check(route === ROUTE_LINK, "and it stays at the LINK layer");
    check(!ack_expected, "LMPs are not acknowledged this way");

    pv=1; ht=5'h04; hco=1; pp=0; pco=1; step;
    check(pkt_type === PKT_TP, "type 0x04 is a Transaction Packet");
    check(route === ROUTE_PROTOCOL, "which goes up to the protocol layer");
    check(ack_expected, "and is acknowledged");

    pv=1; ht=5'h0C; hco=1; pp=0; pco=1; step;
    check(pkt_type === PKT_ITP, "type 0x0C is an Isochronous Timestamp Packet");
    check(route === ROUTE_PROTOCOL, "which is delivered");
    check(!ack_expected,
          "but NEVER acknowledged: a timestamp is stale once questioned");

    // a Data Packet with a bad payload still routes
    pv=1; ht=5'h08; hco=1; pp=1; pco=0; step;
    check(pkt_type === PKT_DP, "type 0x08 is a Data Packet");
    check(payload_error, "its payload CRC failed");
    check(route === ROUTE_PROTOCOL,
          "and it is STILL routed: the header says which endpoint to re-ask");
    check(protocol_nak_req, "with a PROTOCOL-level NAK");
    check(!link_retry_req, "and NOT a link retry");

    // a bad header is a different problem entirely
    pv=1; ht=5'h08; hco=0; pp=1; pco=1; step;
    check(pkt_type === PKT_UNKNOWN,
          "a corrupt header yields UNKNOWN, not the type its bits spell");
    check(route === ROUTE_DISCARD, "and the packet reaches nobody");
    check(link_retry_req, "recovery is a LINK retry");
    check(!protocol_nak_req, "and not a protocol NAK");
    check(!payload_error,
          "and no payload error is claimed: there is no trustworthy header");

    // a reserved type has a GOOD CRC and an uninterpretable type
    pv=1; ht=5'h11; hco=1; pp=0; pco=1; step;
    check(pkt_type === PKT_RSVD, "an undefined encoding is RESERVED");
    check(route === ROUTE_DISCARD, "and is discarded");
    check(!link_retry_req,
          "without a link retry: retrying returns the same reserved value");
    check(!hdr_error, "and it is not a header error -- the CRC was fine");

    // ===== C. randomised =====
    for (i=0;i<40000;i++) begin
      pv=($urandom%8)!=0;
      if (($urandom%3)==0) ht = $urandom%32;
      else ht = 5'(($urandom%4)*4);
      hco=($urandom%8)!=0; pp=$urandom%2; pco=($urandom%4)!=0;
      step;
    end

    foreach (n_pt[i]) check(n_pt[i] > 0, "every packet-type outcome was reached");
    foreach (n_rt[i]) check(n_rt[i] > 0, "every routing outcome was reached");

    $display("");
    $display("  REACH: exhaustive=%0d | types: none=%0d lmp=%0d tp=%0d dp=%0d itp=%0d rsvd=%0d unknown=%0d",
             n_exh, n_pt[0], n_pt[1], n_pt[2], n_pt[3], n_pt[4], n_pt[5], n_pt[6]);
    $display("  ROUTES: none=%0d link=%0d protocol=%0d discard=%0d | hdr-err=%0d payload-err=%0d reserved=%0d delivered=%0d",
             n_rt[0], n_rt[1], n_rt[2], n_rt[3], hdr_errors, payload_errors,
             reserved_types, delivered);
    $display("  [SystemVerilog] usb3_packet_decode: %0d errors", errors);
    $display("  [SystemVerilog] %0s", errors==0 ? "PASS" : "FAIL");
    $display("");
    $finish;
  end
endmodule

12.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_packet_pkg.all;

entity tb_pk_vhdl is
end entity;

architecture sim of tb_pk_vhdl is
  signal clk   : std_logic := '0';
  signal rst_n : std_logic := '0';
  signal pv, hco, pp, pco : std_logic := '0';
  signal ht : std_logic_vector(4 downto 0) := (others => '0');

  signal pkt_type : std_logic_vector(2 downto 0);
  signal route    : std_logic_vector(1 downto 0);
  signal hdr_error, payload_error, link_retry_req : std_logic;
  signal protocol_nak_req, ack_expected : std_logic;
  signal hdr_errors, payload_errors, reserved_types, delivered
       : std_logic_vector(31 downto 0);

  signal running : boolean := true;

  type cnt7_t is array (0 to 6) of integer;
  type cnt4_t is array (0 to 3) of integer;
begin
  clk <= not clk after 5 ns when running else '0';

  dut : entity work.usb3_packet_decode
    port map (clk => clk, rst_n => rst_n, pkt_valid => pv, hdr_type => ht,
              hdr_crc_ok => hco, payload_present => pp, payload_crc_ok => pco,
              pkt_type => pkt_type, route => route, hdr_error => hdr_error,
              payload_error => payload_error, link_retry_req => link_retry_req,
              protocol_nak_req => protocol_nak_req,
              ack_expected => ack_expected, hdr_errors => hdr_errors,
              payload_errors => payload_errors,
              reserved_types => reserved_types, delivered => delivered);

  stim : process
    variable seed1 : positive := 8867;
    variable seed2 : positive := 3391;
    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_pt   : cnt7_t  := (others => 0);
    variable n_rt   : cnt4_t  := (others => 0);
    variable n_exh  : integer := 0;
    variable m_he, m_pe, m_rs, m_dl : integer := 0;

    procedure check(cond : boolean; msg : string) is
    begin
      if not cond then
        errors := errors + 1;
        if errors <= 25 then
          report "  FAIL: " & msg
               & " (pv=" & std_logic'image(pv)(2)
               & " ht=" & integer'image(to_integer(unsigned(ht)))
               & " crc=" & std_logic'image(hco)(2)
               & " pp=" & std_logic'image(pp)(2)
               & " pcrc=" & std_logic'image(pco)(2)
               & " | type=" & integer'image(to_integer(unsigned(pkt_type)))
               & " route=" & integer'image(to_integer(unsigned(route)))
               & ")" 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_type  : pkt_type_t;
      variable e_route : route_t;
      variable e_he, e_pe, e_ack : std_logic;
    begin
      -- The model reaches the same answer by a different route: a chain of
      -- comparisons rather than the design's case statement.
      if pv = '0' then
        e_type := PKT_NONE;
      elsif hco = '0' then
        e_type := PKT_UNKNOWN;
      elsif ht = "00000" then e_type := PKT_LMP;
      elsif ht = "00100" then e_type := PKT_TP;
      elsif ht = "01000" then e_type := PKT_DP;
      elsif ht = "01100" then e_type := PKT_ITP;
      else                    e_type := PKT_RSVD;
      end if;

      if e_type = PKT_NONE then
        e_route := ROUTE_NONE;
      elsif e_type = PKT_UNKNOWN or e_type = PKT_RSVD then
        e_route := ROUTE_DISCARD;
      elsif e_type = PKT_LMP then
        e_route := ROUTE_LINK;
      else
        e_route := ROUTE_PROTOCOL;
      end if;

      if pv = '1' and hco = '0' then e_he := '1'; else e_he := '0'; end if;
      if pv = '1' and hco = '1' and e_type = PKT_DP and pp = '1'
         and pco = '0' then
        e_pe := '1';
      else
        e_pe := '0';
      end if;
      if e_type = PKT_TP or e_type = PKT_DP then
        e_ack := '1';
      else
        e_ack := '0';
      end if;

      check(pkt_type = pkt_code(e_type),   "pkt_type matches the model");
      check(route = route_code(e_route),   "route matches the model");
      check(hdr_error = e_he,              "hdr_error matches the model");
      check(payload_error = e_pe,          "payload_error matches the model");
      check(link_retry_req = e_he,         "link retry follows the header");
      check(protocol_nak_req = e_pe,       "protocol NAK follows the payload");
      check(ack_expected = e_ack,          "only TP and DP are acknowledged");

      -- ---- SAFETY PROPERTIES, independent of the model ----
      -- 1. THE one that matters: with a bad header CRC the type is NOT
      --    decoded. Acting on the type field of a header a CRC has just
      --    declared corrupt routes a packet on untrustworthy bits.
      if pv = '1' and hco = '0' then
        check(pkt_type = pkt_code(PKT_UNKNOWN),
              "a corrupt header was decoded into a packet type anyway");
      -- 2. And such a packet reaches nobody.
        check(route = route_code(ROUTE_DISCARD),
              "a packet with a corrupt header was routed somewhere");
      end if;
      -- 3. The two recoveries are mutually exclusive.
      check(not (link_retry_req = '1' and protocol_nak_req = '1'),
            "a link retry and a protocol NAK requested for one packet");
      -- 4. A payload error is never claimed without a good header.
      check(payload_error = '0' or hco = '1',
            "a payload error claimed with no trustworthy header");
      -- 5. Nothing is routed when no packet is present.
      if pv = '0' then
        check(route = route_code(ROUTE_NONE),
              "a route asserted with no packet");
      end if;

      n_pt(pkt_type_t'pos(e_type)) := n_pt(pkt_type_t'pos(e_type)) + 1;
      n_rt(route_t'pos(e_route))   := n_rt(route_t'pos(e_route)) + 1;
    end procedure;

    procedure step is
    begin
      wait for 1 ns;
      check_comb;
      if pv = '1' then
        if hdr_error = '1' then m_he := m_he + 1; end if;
        if payload_error = '1' then m_pe := m_pe + 1; end if;
        if pkt_type = pkt_code(PKT_RSVD) then m_rs := m_rs + 1; end if;
        if route /= route_code(ROUTE_DISCARD)
           and route /= route_code(ROUTE_NONE) then
          m_dl := m_dl + 1;
        end if;
      end if;
      wait until rising_edge(clk);
      wait for 1 ns;
      check(hdr_errors = std_logic_vector(to_unsigned(m_he, 32)),
            "hdr_errors matches the model");
      check(payload_errors = std_logic_vector(to_unsigned(m_pe, 32)),
            "payload_errors matches the model");
      check(reserved_types = std_logic_vector(to_unsigned(m_rs, 32)),
            "reserved_types matches the model");
      check(delivered = std_logic_vector(to_unsigned(m_dl, 32)),
            "delivered matches the model");
    end procedure;

    procedure hard_reset is
    begin
      rst_n <= '0'; pv <= '0'; ht <= (others => '0');
      hco <= '0'; pp <= '0'; pco <= '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_he := 0; m_pe := 0; m_rs := 0; m_dl := 0;
    end procedure;

    variable iv : integer;
  begin
    hard_reset;
    check(pkt_type = pkt_code(PKT_NONE), "no packet, no type");
    check(route = route_code(ROUTE_NONE), "and no route");

    -- ===== A. EXHAUSTIVE over the whole decode =====
    -- 32 header-type encodings x header CRC ok/bad x payload present/absent
    -- x payload CRC ok/bad x packet valid/not = 512 points -- the ENTIRE
    -- decision surface, including all 28 reserved type encodings.
    for v in 0 to 1 loop
      for t in 0 to 31 loop
        for c in 0 to 1 loop
          for q in 0 to 1 loop
            for r in 0 to 1 loop
              if v = 1 then pv <= '1'; else pv <= '0'; end if;
              ht <= std_logic_vector(to_unsigned(t, 5));
              if c = 1 then hco <= '1'; else hco <= '0'; end if;
              if q = 1 then pp <= '1';  else pp <= '0';  end if;
              if r = 1 then pco <= '1'; else pco <= '0'; end if;
              step;
              n_exh := n_exh + 1;
            end loop;
          end loop;
        end loop;
      end loop;
    end loop;
    report "  exhaustive packet-decode sweep: " & integer'image(n_exh)
         & " of 512 points verified" severity note;

    -- ===== B. directed: the four types and the two failures =====
    hard_reset;
    pv <= '1'; ht <= "00000"; hco <= '1'; pp <= '0'; pco <= '1'; step;
    check(pkt_type = pkt_code(PKT_LMP),
          "type 0x00 is a Link Management Packet");
    check(route = route_code(ROUTE_LINK), "and it stays at the LINK layer");
    check(ack_expected = '0', "LMPs are not acknowledged this way");

    pv <= '1'; ht <= "00100"; hco <= '1'; pp <= '0'; pco <= '1'; step;
    check(pkt_type = pkt_code(PKT_TP), "type 0x04 is a Transaction Packet");
    check(route = route_code(ROUTE_PROTOCOL),
          "which goes up to the protocol layer");
    check(ack_expected = '1', "and is acknowledged");

    pv <= '1'; ht <= "01100"; hco <= '1'; pp <= '0'; pco <= '1'; step;
    check(pkt_type = pkt_code(PKT_ITP),
          "type 0x0C is an Isochronous Timestamp Packet");
    check(route = route_code(ROUTE_PROTOCOL), "which is delivered");
    check(ack_expected = '0',
          "but NEVER acknowledged: a timestamp is stale once questioned");

    -- a Data Packet with a bad payload still routes
    pv <= '1'; ht <= "01000"; hco <= '1'; pp <= '1'; pco <= '0'; step;
    check(pkt_type = pkt_code(PKT_DP), "type 0x08 is a Data Packet");
    check(payload_error = '1', "its payload CRC failed");
    check(route = route_code(ROUTE_PROTOCOL),
          "and it is STILL routed: the header says which endpoint to re-ask");
    check(protocol_nak_req = '1', "with a PROTOCOL-level NAK");
    check(link_retry_req = '0', "and NOT a link retry");

    -- a bad header is a different problem entirely
    pv <= '1'; ht <= "01000"; hco <= '0'; pp <= '1'; pco <= '1'; step;
    check(pkt_type = pkt_code(PKT_UNKNOWN),
          "a corrupt header yields UNKNOWN, not the type its bits spell");
    check(route = route_code(ROUTE_DISCARD), "and the packet reaches nobody");
    check(link_retry_req = '1', "recovery is a LINK retry");
    check(protocol_nak_req = '0', "and not a protocol NAK");
    check(payload_error = '0',
          "and no payload error is claimed: there is no trustworthy header");

    -- a reserved type has a GOOD CRC and an uninterpretable type
    pv <= '1'; ht <= "10001"; hco <= '1'; pp <= '0'; pco <= '1'; step;
    check(pkt_type = pkt_code(PKT_RSVD), "an undefined encoding is RESERVED");
    check(route = route_code(ROUTE_DISCARD), "and is discarded");
    check(link_retry_req = '0',
          "without a link retry: retrying returns the same reserved value");
    check(hdr_error = '0',
          "and it is not a header error -- the CRC was fine");

    -- ===== C. randomised =====
    for i in 0 to 39999 loop
      rnd(iv, 8); if iv /= 0 then pv <= '1'; else pv <= '0'; end if;
      rnd(iv, 3);
      if iv = 0 then
        rnd(iv, 32);
        ht <= std_logic_vector(to_unsigned(iv, 5));
      else
        rnd(iv, 4);
        ht <= std_logic_vector(to_unsigned(iv * 4, 5));
      end if;
      rnd(iv, 8); if iv /= 0 then hco <= '1'; else hco <= '0'; end if;
      rnd(iv, 2); if iv = 1 then pp <= '1'; else pp <= '0'; end if;
      rnd(iv, 4); if iv /= 0 then pco <= '1'; else pco <= '0'; end if;
      step;
    end loop;

    for i in 0 to 6 loop
      check(n_pt(i) > 0, "every packet-type outcome was reached");
    end loop;
    for i in 0 to 3 loop
      check(n_rt(i) > 0, "every routing outcome was reached");
    end loop;

    report "  REACH: exhaustive=" & integer'image(n_exh)
         & " | types: none=" & integer'image(n_pt(0))
         & " lmp=" & integer'image(n_pt(1))
         & " tp=" & integer'image(n_pt(2))
         & " dp=" & integer'image(n_pt(3))
         & " itp=" & integer'image(n_pt(4))
         & " rsvd=" & integer'image(n_pt(5))
         & " unknown=" & integer'image(n_pt(6)) severity note;
    report "  ROUTES: none=" & integer'image(n_rt(0))
         & " link=" & integer'image(n_rt(1))
         & " protocol=" & integer'image(n_rt(2))
         & " discard=" & integer'image(n_rt(3))
         & " | hdr-err=" & integer'image(to_integer(unsigned(hdr_errors)))
         & " payload-err=" & integer'image(to_integer(unsigned(payload_errors)))
         & " reserved=" & integer'image(to_integer(unsigned(reserved_types)))
         & " delivered=" & integer'image(to_integer(unsigned(delivered)))
         severity note;
    report "  [VHDL] usb3_packet_decode: " & 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;

13. Exhaustive Verification, and What "Exhaustive" Bought

All three implementations pass, and the reach figures confirm the sweep actually landed everywhere it claimed to:

MeasureVerilogSystemVerilogVHDL
Exhaustive points512 / 512512 / 512512 / 512
PKT_NONE reached521952195185
PKT_LMP reached558755045369
PKT_TP reached535454105404
PKT_DP reached544853955503
PKT_ITP reached540854115452
PKT_RSVD reached902290999063
PKT_UNKNOWN reached448044804542
ROUTE_LINK558755045369
ROUTE_PROTOCOL162101621616359
ROUTE_DISCARD135021357913605
Header errors counted435243524414
Payload errors counted695677694
Packets delivered217812170421712
ResultPASSPASSPASS

Every one of the seven pkt_type outcomes and all four route outcomes were reached, which the testbenches assert rather than merely report. PKT_RSVD is the most-reached outcome because 28 of the 32 encodings produce it — a reminder that in the real input space, "a type I do not recognise" is the common case, not the exotic one.

14. Mutation Testing

A passing testbench proves nothing about the testbench. Seven single-change mutants were injected into each implementation and the suites re-run; the numbers are the failure counts each mutant produced.

#MutationVerilogSysVerVHDL
K1Decode the type before checking the CRC167401664016931
K2A reserved type also reports hdr_error180461820018128
K3A Data Packet with a bad payload is discarded697679703
K4ITP is acknowledged like TP and DP540954125453
K5payload_error no longer requires a payload138813021340
K6LMP routes to the protocol layer558855055370
K7delivered counts discarded packets too402584025840258
—unmutated baseline000

Every mutant dies, in every language, and no two mutants produce the same count — so no two of them are being caught by the same single check.

K1 is the mutation this chapter exists for. It is the "decode first, validate after" error from section 4, injected verbatim, and it produces roughly 16 700 failures. Worth noticing why that number is so large: the mutation does not merely mis-report one signal. Removing PKT_UNKNOWN changes pkt_type, which changes route, which changes ack_expected, which changes delivered — one wrong line propagating into four observables. In real silicon it would propagate into four behaviours, which is why a bug of this shape is so hard to localise from a symptom.

K3 is the smallest killer at ~690 failures, and that is expected. It fires only on a Data Packet that has a good header, a payload present, and a bad payload CRC — a four-way conjunction. The randomised phase generates about 690 of those in 40 000 cycles, and the exhaustive sweep contributes exactly one point. A count that low is a warning to check why it is low: here it is a genuinely narrow input condition, and all three languages agree on it to within 3%.

K2 and K6 are the two that a careless reviewer would call harmless. K2 asks for a link retry on a reserved type — "surely that is just a wasted retry?" It is a wasted retry that recurs every time the same transmitter sends the same packet, which on a device from a newer specification revision is a permanent error storm on a link that has nothing wrong with it. K6 routes LMPs to the protocol layer, where they arrive at an endpoint that has no idea what to do with them; the link-layer state machine that was waiting for them, meanwhile, times out.

15. Debugging Walkthrough: Rare Corruption on an Idle Endpoint

This is the K1 bug as it presents in the field, and the sequence of deductions that finds it.

The report. A storage device occasionally returns a block containing a few bytes of what is obviously somebody else's data — a fragment of an audio stream, in one memorable case. It happens perhaps once an hour, only with certain cables, and never on the bench.

Step 1 — is the link healthy? Read the link error counters. They are non-zero: a few LBAD events per hour. That looks like it explains the problem — a marginal cable, occasional bit errors — and the tempting conclusion is "replace the cable." Resist it. A marginal cable causes retries, and retries are supposed to be invisible. Errors being detected is the system working. The question is what happens next.

Step 2 — correlate. Timestamp the corruption events against the LBAD events. They coincide. So corruption happens when an error is detected, not when one is missed. That inverts the whole investigation: the CRC is doing its job, and something downstream of the CRC is not.

Step 3 — which CRC? Add separate counters for header-CRC and payload-CRC failures — the hdr_errors and payload_errors outputs of the design in this chapter exist for exactly this reason. The corruption correlates with header failures only. Payload failures are recovered cleanly.

Step 4 — the decisive trace. Capture pkt_type and route on the cycles where hdr_crc_ok is low. In a correct receiver they read UNKNOWN and DISCARD. Here they read a plausible type and a valid route — and the route is not always the same one.

That is the whole bug, visible in one cursor position. The receiver is reading the type and routing fields out of a header the CRC has just rejected, and a flipped bit in the routing field occasionally addresses a different endpoint.

Step 5 — why it never reproduced on the bench. The bug requires a physical-layer bit error and for that error to land in a routing or type field rather than anywhere else in the fourteen bytes. Short bench cables produce almost no bit errors, so the trigger is absent. The bug scales with cable quality, which is exactly the property that makes it look like a cable problem.

16. UVM: Verifying the Untrustworthy-Header Path

The interesting part of this design is only reachable by injecting errors, which makes it a natural fit for a constrained-random UVM environment where corruption is a first-class stimulus rather than an afterthought.

16.1 The transaction

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class usb3_pkt_item extends uvm_sequence_item;
  `uvm_object_utils(usb3_pkt_item)

  rand bit        pkt_valid;
  rand bit [4:0]  hdr_type;
  rand bit        hdr_crc_ok;
  rand bit        payload_present;
  rand bit        payload_crc_ok;

  // The four defined encodings, so a "legal" packet can be asked for
  // without the sequence having to know the numbers.
  constraint c_defined_type {
    soft hdr_type inside {5'h00, 5'h04, 5'h08, 5'h0C};
  }

  // Errors are rare in the field. Making them rare here too means the
  // ordinary paths get exercised; the directed sequences below override
  // this when they want the error path specifically.
  constraint c_errors_rare {
    hdr_crc_ok     dist {1 := 95, 0 := 5};
    payload_crc_ok dist {1 := 95, 0 := 5};
  }

  // Only a Data Packet carries a payload.
  constraint c_payload_only_dp {
    (hdr_type != 5'h08) -> payload_present == 0;
  }

  function new(string name = "usb3_pkt_item");
    super.new(name);
  endfunction

  function string convert2string();
    return $sformatf("valid=%0b type=0x%02h hcrc=%0b pp=%0b pcrc=%0b",
                     pkt_valid, hdr_type, hdr_crc_ok, payload_present,
                     payload_crc_ok);
  endfunction
endclass

The c_payload_only_dp constraint deserves comment. It models the protocol faithfully — only Data Packets have payloads — but it also hides a bug class, because with it in force payload_present is never 1 on a non-DP packet and mutation K5 becomes harder to kill. That is why the error-injection sequence below deliberately relaxes it: a receiver must behave correctly against a broken transmitter, and a constraint that only generates legal traffic cannot prove that.

16.2 Sequences

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Ordinary traffic: legal types, errors at field-realistic rates.
class nominal_traffic_seq extends uvm_sequence #(usb3_pkt_item);
  `uvm_object_utils(nominal_traffic_seq)
  function new(string name = "nominal_traffic_seq"); super.new(name); endfunction

  task body();
    repeat (2000) begin
      usb3_pkt_item it = usb3_pkt_item::type_id::create("it");
      start_item(it);
      if (!it.randomize() with { pkt_valid dist {1 := 90, 0 := 10}; })
        `uvm_error("RAND", "nominal randomize failed")
      finish_item(it);
    end
  endtask
endclass

// THE sequence for this chapter: every packet has a corrupt header, and the
// type field is randomised over all 32 encodings. A receiver that decodes
// before validating will route these somewhere, and the scoreboard will see
// it. This is the K1 mutation's natural predator.
class corrupt_header_seq extends uvm_sequence #(usb3_pkt_item);
  `uvm_object_utils(corrupt_header_seq)
  function new(string name = "corrupt_header_seq"); super.new(name); endfunction

  task body();
    repeat (500) begin
      usb3_pkt_item it = usb3_pkt_item::type_id::create("it");
      start_item(it);
      // c_defined_type is soft, so this override is legal and the full
      // 32-encoding space is explored under a failing CRC.
      if (!it.randomize() with { pkt_valid  == 1;
                                 hdr_crc_ok == 0;
                                 hdr_type inside {[0:31]}; })
        `uvm_error("RAND", "corrupt-header randomize failed")
      finish_item(it);
    end
  endtask
endclass

// Reserved encodings under a GOOD CRC -- the third case from section 5.
// The property under test is that these are discarded WITHOUT a retry.
class reserved_type_seq extends uvm_sequence #(usb3_pkt_item);
  `uvm_object_utils(reserved_type_seq)
  function new(string name = "reserved_type_seq"); super.new(name); endfunction

  task body();
    repeat (500) begin
      usb3_pkt_item it = usb3_pkt_item::type_id::create("it");
      start_item(it);
      if (!it.randomize() with { pkt_valid  == 1;
                                 hdr_crc_ok == 1;
                                 !(hdr_type inside {5'h00, 5'h04,
                                                    5'h08, 5'h0C}); })
        `uvm_error("RAND", "reserved-type randomize failed")
      finish_item(it);
    end
  endtask
endclass

// A malformed transmitter: payloads on packets that should not have them,
// and missing payloads on Data Packets. Relaxes c_payload_only_dp on
// purpose -- see the note above section 16.2.
class malformed_tx_seq extends uvm_sequence #(usb3_pkt_item);
  `uvm_object_utils(malformed_tx_seq)
  function new(string name = "malformed_tx_seq"); super.new(name); endfunction

  task body();
    repeat (500) begin
      usb3_pkt_item it = usb3_pkt_item::type_id::create("it");
      start_item(it);
      it.c_payload_only_dp.constraint_mode(0);
      if (!it.randomize() with { pkt_valid == 1; hdr_crc_ok == 1; })
        `uvm_error("RAND", "malformed randomize failed")
      finish_item(it);
    end
  endtask
endclass

16.3 The scoreboard, and the one check that matters

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class usb3_pkt_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(usb3_pkt_scoreboard)

  uvm_analysis_imp #(usb3_pkt_mon_item, usb3_pkt_scoreboard) ap;

  int unsigned n_unknown, n_reserved, n_delivered, n_retry, n_nak;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction

  function void write(usb3_pkt_mon_item t);
    // ---- THE check. A header the CRC rejected must not have been read. ----
    if (t.pkt_valid && !t.hdr_crc_ok) begin
      if (t.pkt_type !== PKT_UNKNOWN)
        `uvm_error("HDR_TRUST",
          $sformatf("corrupt header decoded as %s -- the type field was read",
                    t.pkt_type.name()))
      if (t.route !== ROUTE_DISCARD)
        `uvm_error("HDR_TRUST",
          $sformatf("corrupt header routed to %s", t.route.name()))
      if (!t.link_retry_req)
        `uvm_error("RECOVERY", "corrupt header did not request a link retry")
      if (t.protocol_nak_req)
        `uvm_error("RECOVERY",
                   "corrupt header requested a PROTOCOL nak -- with what endpoint?")
      n_unknown++;
    end

    // ---- A reserved type is NOT a header error and must NOT be retried ----
    if (t.pkt_type === PKT_RSVD) begin
      if (t.link_retry_req)
        `uvm_error("RETRY_STORM",
                   "reserved type requested a link retry; the retry will return it again")
      if (t.hdr_error)
        `uvm_error("CLASSIFY", "reserved type reported as a header error")
      n_reserved++;
    end

    // ---- A bad payload is still DELIVERED, flagged, never dropped ----
    if (t.pkt_type === PKT_DP && t.payload_present && !t.payload_crc_ok) begin
      if (t.route !== ROUTE_PROTOCOL)
        `uvm_error("DP_DROP",
          "a Data Packet with a bad payload was dropped -- the protocol layer now cannot say what to re-request")
      if (!t.protocol_nak_req)
        `uvm_error("RECOVERY", "bad payload did not request a protocol NAK")
      n_nak++;
    end

    // ---- The two recoveries can never both be right ----
    if (t.link_retry_req && t.protocol_nak_req)
      `uvm_error("EXCLUSIVE",
                 "both a link retry and a protocol NAK for one packet")

    if (t.link_retry_req) n_retry++;
    if (t.route === ROUTE_LINK || t.route === ROUTE_PROTOCOL) n_delivered++;
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("SB", $sformatf(
      "unknown=%0d reserved=%0d delivered=%0d link-retries=%0d protocol-naks=%0d",
      n_unknown, n_reserved, n_delivered, n_retry, n_nak), UVM_LOW)

    // A run in which the error paths were never reached proves nothing.
    if (n_unknown  == 0) `uvm_error("COVERAGE", "no corrupt header was ever seen")
    if (n_reserved == 0) `uvm_error("COVERAGE", "no reserved type was ever seen")
    if (n_nak      == 0) `uvm_error("COVERAGE", "no payload error was ever seen")
  endfunction
endclass

Those three report_phase checks are the habit worth stealing. A UVM run that reports zero errors because it never reached the interesting states is worse than a failing run, because it is indistinguishable from a good one in a regression summary. Asserting that the error paths were exercised turns "nothing broke" into "the thing that could break was tried."

16.4 Functional coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup pkt_decode_cg with function sample(
    bit valid, bit [4:0] ht, bit hcrc, bit pp, bit pcrc,
    pkt_type_e pt, route_e rt);

  cp_type : coverpoint pt {
    bins defined[]  = {PKT_LMP, PKT_TP, PKT_DP, PKT_ITP};
    bins reserved   = {PKT_RSVD};
    bins unknown    = {PKT_UNKNOWN};     // the one that matters
    bins idle       = {PKT_NONE};
  }

  cp_route : coverpoint rt { bins all[] = {ROUTE_NONE, ROUTE_LINK,
                                          ROUTE_PROTOCOL, ROUTE_DISCARD}; }

  // All 32 encodings individually, not four plus "other".
  cp_encoding : coverpoint ht   { bins enc[32] = {[0:31]}; }
  cp_hcrc     : coverpoint hcrc { bins ok = {1}; bins bad = {0}; }
  cp_pp       : coverpoint pp   { bins present = {1}; bins absent = {0}; }
  cp_pcrc     : coverpoint pcrc { bins ok = {1}; bins bad = {0}; }

  // THE cross: every type encoding seen under BOTH CRC outcomes. This is
  // what proves the decode was gated rather than merely correct on good
  // headers -- and it is 64 bins, all of them reachable. A coverpoint must
  // be declared before a cross names it.
  x_enc_crc : cross cp_encoding, cp_hcrc;

  // A Data Packet under every combination of payload presence and payload
  // CRC -- the four-way conjunction that mutation K3 lives in.
  x_dp_payload : cross cp_type, cp_pp, cp_pcrc {
    ignore_bins non_dp = binsof(cp_type) intersect {PKT_LMP, PKT_TP,
                                                    PKT_ITP, PKT_NONE};
  }
endgroup

x_enc_crc is the coverage goal that corresponds to the design's central rule. Closing it means every one of the 32 type encodings was presented both with a passing header CRC and with a failing one — which is precisely the evidence that the CRC gate was tested, not just the decode.

17. SystemVerilog Assertions

The properties are all single-cycle, because the decode is combinational. That makes them cheap enough to leave enabled in every regression.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// bind this module to the DUT
module usb3_packet_decode_sva
  import usb3_packet_pkg::*;
(
  input logic       clk,
  input logic       rst_n,
  input logic       pkt_valid,
  input logic [4:0] hdr_type,
  input logic       hdr_crc_ok,
  input logic       payload_present,
  input logic       payload_crc_ok,
  input pkt_type_e  pkt_type,
  input route_e     route,
  input logic       hdr_error,
  input logic       payload_error,
  input logic       link_retry_req,
  input logic       protocol_nak_req,
  input logic       ack_expected
);
  default clocking cb @(posedge clk); endclocking
  default disable iff (!rst_n);

  // ---- 1. THE property. A rejected header is not interpreted. ----
  property p_corrupt_header_not_decoded;
    (pkt_valid && !hdr_crc_ok) |-> (pkt_type == PKT_UNKNOWN);
  endproperty
  a_corrupt_header_not_decoded : assert property (p_corrupt_header_not_decoded)
    else $error("a header the CRC rejected was decoded as %s", pkt_type.name());

  // ---- 2. ...and it reaches nobody. ----
  property p_corrupt_header_discarded;
    (pkt_valid && !hdr_crc_ok) |-> (route == ROUTE_DISCARD);
  endproperty
  a_corrupt_header_discarded : assert property (p_corrupt_header_discarded);

  // ---- 3. The two recoveries are mutually exclusive. ----
  property p_recoveries_exclusive;
    not (link_retry_req && protocol_nak_req);
  endproperty
  a_recoveries_exclusive : assert property (p_recoveries_exclusive)
    else $error("a link retry and a protocol NAK for the same packet");

  // ---- 4. A payload error implies a trustworthy header. ----
  property p_payload_error_needs_header;
    payload_error |-> hdr_crc_ok;
  endproperty
  a_payload_error_needs_header : assert property (p_payload_error_needs_header);

  // ---- 5. A reserved type never provokes a retry. ----
  property p_reserved_no_retry;
    (pkt_type == PKT_RSVD) |-> !link_retry_req;
  endproperty
  a_reserved_no_retry : assert property (p_reserved_no_retry)
    else $error("a retry was requested for a packet that arrived intact");

  // ---- 6. A bad payload is delivered, not dropped. ----
  property p_bad_payload_still_routed;
    (pkt_valid && hdr_crc_ok && pkt_type == PKT_DP
      && payload_present && !payload_crc_ok) |-> (route == ROUTE_PROTOCOL);
  endproperty
  a_bad_payload_still_routed : assert property (p_bad_payload_still_routed)
    else $error("a Data Packet with a recoverable payload error was dropped");

  // ---- 7. The timestamp is never acknowledged. ----
  property p_itp_never_acked;
    (pkt_type == PKT_ITP) |-> !ack_expected;
  endproperty
  a_itp_never_acked : assert property (p_itp_never_acked);

  // ---- 8. LMPs never leave the link layer. ----
  property p_lmp_stays_at_link;
    (pkt_type == PKT_LMP) |-> (route == ROUTE_LINK);
  endproperty
  a_lmp_stays_at_link : assert property (p_lmp_stays_at_link);

  // ---- Cover: the error paths were actually exercised. ----
  c_unknown  : cover property ((pkt_valid && !hdr_crc_ok));
  c_reserved : cover property ((pkt_type == PKT_RSVD));
  c_nak      : cover property ((protocol_nak_req));
endmodule

bind usb3_packet_decode usb3_packet_decode_sva u_sva (.*);

Icarus Verilog does not support concurrent assertions, so these were checked as procedural conditions inside the testbenches (sections 12.1–12.3, marked "SAFETY PROPERTIES") and are given here in SVA form for use with a commercial simulator or a formal tool. All eight are single-cycle implications with no temporal depth, which makes them excellent formal targets — a property checker will prove or disprove each of them in seconds, over the full 512-point space, without any stimulus at all.

18. Common Misconceptions

"Two CRCs means the header is double-protected." No — the two CRCs cover disjoint byte ranges. The header is covered by exactly one CRC and the payload by exactly one. What is doubled is not the protection but the classification: the receiver learns not just that something was corrupt but which part, and that is what lets two different layers respond.

"A CRC failure means drop the packet." For the header, yes. For the payload, no — dropping it destroys the routing information the protocol layer needs to recover. The packet is delivered with payload_error asserted. This is the single most common error in a first draft of this block.

"A reserved type is an error." It is an unknown, not an error. The bytes arrived exactly as sent. Treating it as an error produces a permanent retry storm against a device that is merely newer than you are. Count it, drop the packet, keep the link up.

"PKT_UNKNOWN is just a debug value." It is the only truthful output for a rejected header, and making it a distinct value is what prevents the rest of the receiver from fabricating a type. In the VHDL build it is a member of an enumerated type, so a downstream case cannot silently ignore it.

"ITP needs an ACK like everything else." An ACK for a timestamp is worthless — by the time it returns, the time it confirmed has passed — and with many devices on the bus the ACK traffic would congest the bus whose timing the ITP exists to distribute. It is broadcast, fire-and-forget.

"Decode order is a style question." It is the difference between a receiver that discards corrupt packets and one that delivers them to arbitrary endpoints. Mutation K1 measures the gap: 16 700 failing checks from moving two lines.

"The header CRC makes the payload CRC unnecessary for short packets." The two cover different bytes. A payload of one byte is still entirely outside the header CRC's domain, and a bit error in it is invisible to the header check.

19. Exercises

1. Swap the order of the !hdr_crc_ok test and the hdr_type comparisons in the Verilog design, and run the Verilog testbench. Before you run it, predict which of the seven model checks and five safety properties will fail. (Answer: the count is 16 740 — see if you can explain the magnitude, not just the sign.)

2. Widen hdr_type to six bits, making 64 encodings of which four are still defined. Update the exhaustive sweep and confirm it still enumerates the complete space. Which reach counter changes the most, and why is that the expected answer for a forward-compatible receiver?

3. Add an output retry_count that saturates at 3 and forces route to ROUTE_DISCARD with no further retries once saturated — a real receiver must not retry forever. Then write the safety property that a saturated receiver never asserts link_retry_req, and a mutation that breaks it.

4. Mutation K7 saturates at 40 258 in all three languages. Restructure the delivered check so that the counter is compared differentially (did it change by the right amount this cycle?) rather than absolutely, re-run K7, and explain why the new number is both smaller and more informative.

5. The c_payload_only_dp UVM constraint models legal traffic and thereby hides mutation K5. Write a coverage assertion that fails if a regression ever runs without payload_present being 1 on a non-DP packet at least once — i.e. make the hole in the constraint visible in the coverage report rather than silent.

6. Take the eight SVA properties to a formal tool and prove them over the unconstrained input space. Then remove the hdr_crc_ok term from payload_error (the deliberate local redundancy from section 8) and see which property still catches it. Does the design remain correct? Does it remain locally readable?

20. Summary

IdeaWhy it matters
Four packet types: LMP, TP, DP, ITPonly DP carries data; LMP never leaves the link layer
14-byte header, own CRC-16the packet's identity is protected separately from its contents
Payload, own CRC-32a data error and an identity error are different events
Header CRC bad → PKT_UNKNOWNthe type field must not be read; nothing can be reported upward
Header CRC bad → link retryonly the link layer has enough information to act
Payload CRC bad → protocol NAKthe good header names the endpoint to re-ask
The two recoveries are exclusiveby construction: one needs a header the other lacks
A bad payload is delivered, flaggeddropping it destroys the recovery information
28 reserved encodings → discard, no retrythe bytes were intact; a retry returns them unchanged
ITP is never acknowledgeda confirmed timestamp is already stale
512-point exhaustive verificationthe complete decision surface, every reserved encoding
7 mutations, all killed in 3 languagesincluding the decode-order bug at ~16 700 failures

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o pk_v.out pk_v.v pk_v_tb.v && ./pk_v.out
SystemVerilogiverilog -g2012 -o pk_sv.out pk_sv.sv pk_sv_tb.sv && ./pk_sv.out
VHDL-2008 analysenvc --std=2008 -a pk_vhdl.vhd pk_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_pk_vhdl
VHDL-2008 runnvc --std=2008 -r tb_pk_vhdl
One mutationiverilog -g2005 -DMUT_K1 -o mm pk_v_mut.v pk_v_tb.v && ./mm

All three implementations pass with 0 errors: 512 of 512 exhaustive points, 40 000 randomised cycles, and every one of the seven pkt_type outcomes and four route outcomes reached and asserted reached.


Chapter 20.5 — USB 3.x vs USB 2.0 Differences closes the module by putting the two buses side by side. Not as a feature table — as a question a real driver has to answer: given a device operating at some speed, which mechanism applies? Polling or credits. NAK or ERDY. A 100 mA unit load or a 150 mA one. The answer is not "the newer one", because a SuperSpeed-capable device connected through a USB 2 hub is a USB 2 device, and a receiver that assumes otherwise breaks in exactly the way Chapter 20.2 warned about.

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.