Skip to content
VLSI Mentor

USB · Module 25

CRC Errors

Real CRC5 and CRC16, verified against every single-bit and every double-bit error on all 2048 token values — and why the distribution of CRC failures names the broken half of the link while the count says nothing.

Chapter 25.4 put CRC failures in the PHY bucket and moved on. This chapter opens the bucket.

1. The Two Polynomials, and What Each One Covers

USB uses two CRCs, and the difference between them is not their strength — it is what they protect and, crucially, who transmits them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   CRC5    x^5 + x^2 + 1                    poly 0x05
           covers the 11-bit TOKEN field:
           7-bit address + 4-bit endpoint.
           Sent by the HOST, on every single transaction.

   CRC16   x^16 + x^15 + x^2 + 1            poly 0x8005
           covers the DATA payload.
           Sent by whoever has the data, so either direction.

   Both:   initial value all ones
           result complemented

The initial value and the final complement are not decoration. All-ones initialisation makes leading zeros in the data change the result; the final complement makes an all-zero codeword — which is exactly what a dead transmitter or a stuck-at-zero line produces — fail. Without either, the most common physical failure mode produces a perfectly valid frame.

2. The Field Names the Direction

This is what turns a CRC counter into a diagnosis, and it follows from one asymmetry: a token is always host-to-device. There is no such thing as a device-originated token.

What failedWhich half of the link
CRC5downstream (host → device). Always.
CRC16 on IN dataupstream (device → host)
CRC16 on OUT datadownstream
both halvescommon mode: power, ground, the connector, the cable

Which CRC covers which direction

A sequence diagram between a host and a device. For an IN transaction the host sends an IN token protected by CRC5, travelling downstream, and the device replies with data protected by CRC16, travelling upstream. For an OUT transaction the host sends an OUT token protected by CRC5 and then data protected by CRC16, both travelling downstream, and the device replies with a handshake which carries no CRC at all.CRC5 and CRC16 on an IN and an OUT transactionHostDeviceIN token + CRC5 —downstreamDATA + CRC16 —upstreamACK — no CRC at allOUT token + CRC5 —downstreamDATA + CRC16 —downstreamACK — no CRC at all
The token and its CRC5 travel one way only, so a CRC5 failure is unambiguous evidence about the downstream half. Data and its CRC16 travel whichever way the transfer goes, so a CRC16 failure only tells you something once you know the direction of the transfer it belonged to.

3. And the Clustering Names the Mechanism

Two links, same total CRC failure count, entirely different faults:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   scattered singles     . . X . . . . X . . . X . . . . X . .
                         marginal signal. Thermal noise, an eye
                         that is closing, a bad margin.

   runs                  . . . . X X X X X . . . . . . X X X X .
                         something PERIODIC. EMI from a switching
                         supply, a fan, a motor, a clock spur.

So a failure is counted burst when the previous checked codeword also failed, and isolated otherwise. A run of five is one isolated and four burst; five failures each separated by a good codeword are five isolated and zero burst. The ratio is the measurement, and the testbench checks both of those exact cases.

4. A Receiver Checks the Residual

A receiver does not recompute the CRC and compare it against the received one. It feeds the whole codeword — data and the received CRC — through the same shift register, and the result is a constant:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   CRC5  residual = 0x0C      for all 2048 token values
   CRC16 residual = 0x800D

One register instead of two, and a comparison against a constant instead of against a computed value.

5. What the Polynomial Actually Promises

A CRC is chosen for its detection properties, not for producing a repeatable number. Any hash produces a repeatable number. What CRC5 with x^5 + x^2 + 1 guarantees over a 16-bit codeword is:

  • every single-bit error is detected
  • every double-bit error is detected
  • every burst error up to 5 bits is detected

So those are the properties the testbench checks, exhaustively, rather than checking a handful of values:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   2048 tokens x 16 bit positions        =  32768 single-bit errors
   2048 tokens x C(16,2) = 120 pairs     = 245760 double-bit errors

   undetected: 0 and 0.

6. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_crc_analyzer -- the two real USB CRCs, and the reason the count of
// CRC failures is nearly useless while the DISTRIBUTION of them is the most
// direct evidence about the physical layer you will ever get.
//
// THE TWO POLYNOMIALS, AND WHAT EACH ONE COVERS
//
//     CRC5    x^5 + x^2 + 1                          poly 0x05
//             covers the 11-bit TOKEN field: 7-bit address + 4-bit
//             endpoint. Sent by the HOST, every single transaction.
//
//     CRC16   x^16 + x^15 + x^2 + 1                  poly 0x8005
//             covers the DATA payload. Sent by whoever has the data, so
//             either direction.
//
// Both are initialised to all ones and both have their result complemented,
// which is not decoration: it makes a codeword of all zeros -- the thing a
// dead receiver produces -- fail, and it makes leading zeros in the data
// change the result.
//
// THE FIELD NAMES THE DIRECTION
//
// This is the part that turns a CRC counter into a diagnosis. A token is
// ALWAYS host-to-device. So:
//
//     CRC5 failures        the DOWNSTREAM half is damaged. Always.
//     CRC16 on IN data     the UPSTREAM half is damaged.
//     CRC16 on OUT data    the DOWNSTREAM half is damaged.
//     both halves          common mode: power, ground, the connector,
//                          or the cable as a whole.
//
// A device that reports thousands of CRC16 failures on IN transfers and no
// CRC5 failures at all has a perfectly good downstream path -- which rules
// out half the usual suspects on the first reading of the counter.
//
// AND THE CLUSTERING NAMES THE MECHANISM
//
//     scattered single failures   marginal signal. Thermal noise,
//                                 an eye that is closing, a bad margin.
//
//     failures in RUNS            something periodic. EMI from a switching
//                                 supply, a fan, a motor, a clock spur.
//
// Same total. Completely different thing to go and look at. So this block
// counts a failure as BURST when the previous checked codeword also failed,
// and as ISOLATED otherwise -- a run of five failures is one isolated and
// four burst, and the ratio is the measurement.
//
// A RECEIVER CHECKS THE RESIDUAL, IT DOES NOT RECOMPUTE AND COMPARE
//
// Feeding the WHOLE codeword -- data and the received CRC together --
// through the same shift register leaves a CONSTANT, whatever the data was.
// It is one register instead of two and a comparison against a constant
// instead of against a computed value.
//
//     CRC5  residual = 5'h0C     for all 2048 token values
//     CRC16 residual = 16'h800D
//
// Those two constants are checked exhaustively by the testbench rather than
// quoted, because a wrong residual constant produces a checker that passes
// everything, and nothing else in the system will notice.
module usb_crc_analyzer #(
  parameter integer BURST_MIN = 2   // a run of this length is a burst
) (
  input  wire        clk,
  input  wire        rst_n,

  // ---- the token: 11 bits plus its CRC5, always host-to-device ----
  input  wire        tok_valid,
  input  wire [10:0] tok_data,     // {endpoint[3:0], address[6:0]}
  input  wire [4:0]  tok_crc5,     // as received

  // ---- the data packet: a byte stream plus its CRC16 ----
  input  wire        dat_start,    // begin a packet
  input  wire        dat_valid,    // one payload byte
  input  wire [7:0]  dat_byte,
  input  wire        dat_end,      // end of packet; dat_crc16 is valid now
  input  wire [15:0] dat_crc16,    // as received
  input  wire        dat_dir,      // 0 = OUT (downstream), 1 = IN (upstream)

  input  wire        eot,

  output wire [4:0]  crc5_gen,     // what the CRC5 SHOULD be
  output wire [4:0]  crc5_resid,   // the residual of the received codeword
  output wire        crc5_ok,
  output wire [15:0] crc16_gen,
  output wire [15:0] crc16_resid,
  output wire        crc16_ok,

  output wire        fail_pulse,
  output wire        fail_field,   // 0 = token (CRC5), 1 = data (CRC16)
  output wire [7:0]  fail_run,     // consecutive failures
  output wire [2:0]  verdict,
  output wire        bursty,

  output reg [31:0] n_tok,
  output reg [31:0] n_tok_fail,
  output reg [31:0] n_dat,
  output reg [31:0] n_dat_fail,
  output reg [31:0] n_down_fail,   // host -> device
  output reg [31:0] n_up_fail,     // device -> host
  output reg [31:0] n_burst,
  output reg [31:0] n_isolated
);

  localparam [4:0]  CRC5_INIT  = 5'h1F,  CRC5_POLY  = 5'h05;
  localparam [15:0] CRC16_INIT = 16'hFFFF, CRC16_POLY = 16'h8005;

  // The two residual constants. Named, so that the testbench can prove them
  // rather than the design asserting them.
  localparam [4:0]  CRC5_RESID  = 5'h0C;
  localparam [15:0] CRC16_RESID = 16'h800D;

  // ---- The burst threshold is derived once, at full width. ----
  //
  // Writing `run_r >= BURST_MIN[7:0] - 8'd1` inline works. What it hides is
  // that the expression TRUNCATES the parameter to eight bits: it is correct
  // here only because a run counter is eight bits, and it would be silently
  // wrong the moment either width changed without the other.
  //
  // Chapter 25.4's retry budget is the same trap with teeth -- there, a
  // two-bit comparison turned MAX_RETRY = 5 into 1 and no test written
  // against the default value noticed. The rule that comes out of it:
  // derive a width from what the value MEANS, never from what its default
  // happens to need.
  localparam [7:0] BURST_THRESH = BURST_MIN - 1;

  localparam [2:0] V_CLEAN  = 3'd0,  // nothing failed
                   V_DOWN   = 3'd1,  // host -> device is damaged
                   V_UP     = 3'd2,  // device -> host is damaged
                   V_COMMON = 3'd3;  // both: power, ground, the connector

  // ---- One bit into the CRC5 shift register. ----
  //
  // Data goes in LSB first, which is the order it is sent on the wire. Get
  // this backwards and every value is wrong, all 2048 of them, so the
  // spec's own worked example -- address 0x15, endpoint 0xE, CRC5 0x17 --
  // is the first thing the testbench checks.
  function [4:0] crc5_step;
    input [4:0] c;
    input       b;
    reg         x;
    begin
      x = b ^ c[4];
      crc5_step = {c[3:0], 1'b0};
      if (x) crc5_step = crc5_step ^ CRC5_POLY;
    end
  endfunction

  function [4:0] crc5_of;
    input [10:0] d;
    reg [4:0] c;
    integer   i;
    begin
      c = CRC5_INIT;
      for (i = 0; i < 11; i = i + 1) c = crc5_step(c, d[i]);
      crc5_of = ~c;
    end
  endfunction

  // ---- The RESIDUAL: the same register, fed the CRC as well. ----
  //
  // The received CRC goes in MSB first, because that is the order it
  // arrives in relative to the shift register's own direction.
  function [4:0] crc5_residual_of;
    input [10:0] d;
    input [4:0]  r;
    reg [4:0] c;
    integer   i;
    begin
      c = CRC5_INIT;
      for (i = 0; i < 11; i = i + 1) c = crc5_step(c, d[i]);
      for (i = 4; i >= 0; i = i - 1) c = crc5_step(c, r[i]);
      crc5_residual_of = c;
    end
  endfunction

  function [15:0] crc16_step;
    input [15:0] c;
    input        b;
    reg          x;
    begin
      x = b ^ c[15];
      crc16_step = {c[14:0], 1'b0};
      if (x) crc16_step = crc16_step ^ CRC16_POLY;
    end
  endfunction

  function [15:0] crc16_byte;
    input [15:0] c;
    input [7:0]  b;
    reg [15:0] t;
    integer    i;
    begin
      t = c;
      for (i = 0; i < 8; i = i + 1) t = crc16_step(t, b[i]);
      crc16_byte = t;
    end
  endfunction

  function [15:0] crc16_absorb16;
    input [15:0] c;
    input [15:0] r;
    reg [15:0] t;
    integer    i;
    begin
      t = c;
      for (i = 15; i >= 0; i = i - 1) t = crc16_step(t, r[i]);
      crc16_absorb16 = t;
    end
  endfunction

  // ---- The token check is purely combinational: 11 bits, one cycle. ----
  wire [4:0] t_gen   = crc5_of(tok_data);
  wire [4:0] t_resid = crc5_residual_of(tok_data, tok_crc5);
  wire       t_ok    = (t_resid == CRC5_RESID);

  assign crc5_gen   = t_gen;
  assign crc5_resid = t_resid;
  assign crc5_ok    = t_ok;

  // ---- The data check accumulates across the packet. ----
  reg [15:0] c16_r;
  wire [15:0] d_gen   = ~c16_r;
  wire [15:0] d_resid = crc16_absorb16(c16_r, dat_crc16);
  wire        d_ok    = (d_resid == CRC16_RESID);

  assign crc16_gen   = d_gen;
  assign crc16_resid = d_resid;
  assign crc16_ok    = d_ok;

  reg [7:0] run_r;
  reg       fp_r, ff_r;
  assign fail_pulse = fp_r;
  assign fail_field = ff_r;
  assign fail_run   = run_r;

  // ---- The verdict. ----
  //
  // A ratio, not a count. "Two thousand CRC errors" says nothing; "every
  // one of them upstream" says the downstream half is fine and halves the
  // search on the first reading.
  assign verdict = ((n_down_fail == 32'd0) && (n_up_fail == 32'd0)) ? V_CLEAN :
                   (n_down_fail > (n_up_fail   << 1))              ? V_DOWN  :
                   (n_up_fail   > (n_down_fail << 1))              ? V_UP    :
                                                                     V_COMMON;

  // Most failures arrived in runs -> something periodic. Scattered ->
  // marginal signal. Same total, different thing to go and look at.
  assign bursty = (n_burst > n_isolated);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c16_r      <= CRC16_INIT;
      run_r      <= 8'd0;
      fp_r       <= 1'b0;
      ff_r       <= 1'b0;
      n_tok      <= 32'd0;
      n_tok_fail <= 32'd0;
      n_dat      <= 32'd0;
      n_dat_fail <= 32'd0;
      n_down_fail<= 32'd0;
      n_up_fail  <= 32'd0;
      n_burst    <= 32'd0;
      n_isolated <= 32'd0;
    end else begin
      fp_r <= 1'b0;

      if (eot) begin
        // Nothing: the counters are the report.
      end else begin
        // ---- the data packet's shift register ----
        //
        // dat_start and dat_valid can be asserted together, so the reset to
        // CRC16_INIT has to happen before the byte is absorbed rather than
        // instead of it. Written the other way round, the first byte of
        // every packet is silently dropped and the CRC is wrong for every
        // packet in exactly the same way -- which looks like a polynomial
        // error and is not.
        if (dat_start && dat_valid)
          c16_r <= crc16_byte(CRC16_INIT, dat_byte);
        else if (dat_start)
          c16_r <= CRC16_INIT;
        else if (dat_valid)
          c16_r <= crc16_byte(c16_r, dat_byte);

        if (tok_valid) begin
          n_tok <= n_tok + 32'd1;
          if (!t_ok) begin
            fp_r       <= 1'b1;
            ff_r       <= 1'b0;
            n_tok_fail <= n_tok_fail + 32'd1;
            // A TOKEN is always host-to-device. There is no ambiguity to
            // resolve and no direction input to get wrong.
            n_down_fail<= n_down_fail + 32'd1;
            if (run_r >= BURST_THRESH) n_burst    <= n_burst    + 32'd1;
            else                       n_isolated <= n_isolated + 32'd1;
            if (run_r != 8'hFF) run_r <= run_r + 8'd1;
          end else begin
            run_r <= 8'd0;
          end
        end else if (dat_end) begin
          n_dat <= n_dat + 32'd1;
          if (!d_ok) begin
            fp_r       <= 1'b1;
            ff_r       <= 1'b1;
            n_dat_fail <= n_dat_fail + 32'd1;
            // Data goes in whichever direction the transfer does, so here
            // the direction has to be told to us.
            if (dat_dir) n_up_fail   <= n_up_fail   + 32'd1;
            else         n_down_fail <= n_down_fail + 32'd1;
            if (run_r >= BURST_THRESH) n_burst    <= n_burst    + 32'd1;
            else                       n_isolated <= n_isolated + 32'd1;
            if (run_r != 8'hFF) run_r <= run_r + 8'd1;
          end else begin
            run_r <= 8'd0;
          end
        end
      end
    end
  end
endmodule

7. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_crc_analyzer -- the two real USB CRCs, and the reason the count of
// CRC failures is nearly useless while the DISTRIBUTION of them is the most
// direct evidence about the physical layer you will ever get.
//
// THE TWO POLYNOMIALS, AND WHAT EACH ONE COVERS
//
//     CRC5    x^5 + x^2 + 1                          poly 0x05
//             covers the 11-bit TOKEN field: 7-bit address + 4-bit
//             endpoint. Sent by the HOST, every single transaction.
//
//     CRC16   x^16 + x^15 + x^2 + 1                  poly 0x8005
//             covers the DATA payload. Sent by whoever has the data, so
//             either direction.
//
// Both are initialised to all ones and both have their result complemented,
// which is not decoration: it makes a codeword of all zeros -- the thing a
// dead receiver produces -- fail, and it makes leading zeros in the data
// change the result.
//
// THE FIELD NAMES THE DIRECTION
//
// This is the part that turns a CRC counter into a diagnosis. A token is
// ALWAYS host-to-device. So:
//
//     CRC5 failures        the DOWNSTREAM half is damaged. Always.
//     CRC16 on IN data     the UPSTREAM half is damaged.
//     CRC16 on OUT data    the DOWNSTREAM half is damaged.
//     both halves          common mode: power, ground, the connector,
//                          or the cable as a whole.
//
// A device that reports thousands of CRC16 failures on IN transfers and no
// CRC5 failures at all has a perfectly good downstream path -- which rules
// out half the usual suspects on the first reading of the counter.
//
// AND THE CLUSTERING NAMES THE MECHANISM
//
//     scattered single failures   marginal signal. Thermal noise,
//                                 an eye that is closing, a bad margin.
//
//     failures in RUNS            something periodic. EMI from a switching
//                                 supply, a fan, a motor, a clock spur.
//
// Same total. Completely different thing to go and look at. So this block
// counts a failure as BURST when the previous checked codeword also failed,
// and as ISOLATED otherwise -- a run of five failures is one isolated and
// four burst, and the ratio is the measurement.
//
// A RECEIVER CHECKS THE RESIDUAL, IT DOES NOT RECOMPUTE AND COMPARE
//
// Feeding the WHOLE codeword -- data and the received CRC together --
// through the same shift register leaves a CONSTANT, whatever the data was.
// It is one register instead of two and a comparison against a constant
// instead of against a computed value.
//
//     CRC5  residual = 5'h0C     for all 2048 token values
//     CRC16 residual = 16'h800D
//
// Those two constants are checked exhaustively by the testbench rather than
// quoted, because a wrong residual constant produces a checker that passes
// everything, and nothing else in the system will notice.
package usb_crc_pkg;
  // The verdict is the whole point of the block, so it gets a type. A
  // three-bit output called `verdict` is a number somebody has to look up;
  // an enumeration prints V_UPSTREAM in the waveform viewer and in the log.
  typedef enum logic [2:0] {
    V_CLEAN  = 3'd0,   // nothing failed
    V_DOWN   = 3'd1,   // host -> device is damaged
    V_UP     = 3'd2,   // device -> host is damaged
    V_COMMON = 3'd3    // both: power, ground, the connector
  } verdict_e;

  typedef enum logic {
    F_TOKEN = 1'b0,    // CRC5 -- always host-to-device
    F_DATA  = 1'b1     // CRC16 -- whichever way the transfer went
  } field_e;
endpackage

module usb_crc_analyzer
  import usb_crc_pkg::*;
 #(
  parameter int BURST_MIN = 2       // a run of this length is a burst
) (
  input  logic       clk,
  input  logic       rst_n,

  // ---- the token: 11 bits plus its CRC5, always host-to-device ----
  input  logic       tok_valid,
  input  logic [10:0] tok_data,    // {endpoint[3:0], address[6:0]}
  input  logic [4:0]  tok_crc5,    // as received

  // ---- the data packet: a byte stream plus its CRC16 ----
  input  logic       dat_start,    // begin a packet
  input  logic       dat_valid,    // one payload byte
  input  logic [7:0] dat_byte,
  input  logic       dat_end,      // end of packet; dat_crc16 is valid now
  input  logic [15:0] dat_crc16,   // as received
  input  logic       dat_dir,      // 0 = OUT (downstream), 1 = IN (upstream)

  input  logic       eot,

  output logic [4:0]  crc5_gen,    // what the CRC5 SHOULD be
  output logic [4:0]  crc5_resid,  // the residual of the received codeword
  output logic        crc5_ok,
  output logic [15:0] crc16_gen,
  output logic [15:0] crc16_resid,
  output logic        crc16_ok,

  output logic        fail_pulse,
  output field_e      fail_field,
  output logic [7:0]  fail_run,    // consecutive failures
  output verdict_e    verdict,
  output logic        bursty,

  output logic [31:0] n_tok,
  output logic [31:0] n_tok_fail,
  output logic [31:0] n_dat,
  output logic [31:0] n_dat_fail,
  output logic [31:0] n_down_fail,   // host -> device
  output logic [31:0] n_up_fail,     // device -> host
  output logic [31:0] n_burst,
  output logic [31:0] n_isolated
);

  localparam [4:0]  CRC5_INIT  = 5'h1F,  CRC5_POLY  = 5'h05;
  localparam [15:0] CRC16_INIT = 16'hFFFF, CRC16_POLY = 16'h8005;

  // The two residual constants. Named, so that the testbench can prove them
  // rather than the design asserting them.
  localparam [4:0]  CRC5_RESID  = 5'h0C;
  localparam [15:0] CRC16_RESID = 16'h800D;

  // ---- The burst threshold is derived once, at full width. ----
  //
  // Writing `run_r >= BURST_MIN[7:0] - 8'd1` inline works. What it hides is
  // that the expression TRUNCATES the parameter to eight bits: it is correct
  // here only because a run counter is eight bits, and it would be silently
  // wrong the moment either width changed without the other.
  //
  // Chapter 25.4's retry budget is the same trap with teeth -- there, a
  // two-bit comparison turned MAX_RETRY = 5 into 1 and no test written
  // against the default value noticed. The rule that comes out of it:
  // derive a width from what the value MEANS, never from what its default
  // happens to need.
  localparam logic [7:0] BURST_THRESH = 8'(BURST_MIN - 1);

  // ---- One bit into the CRC5 shift register. ----
  //
  // Data goes in LSB first, which is the order it is sent on the wire. Get
  // this backwards and every value is wrong, all 2048 of them, so the
  // spec's own worked example -- address 0x15, endpoint 0xE, CRC5 0x17 --
  // is the first thing the testbench checks.
  function automatic logic [4:0] crc5_step(input logic [4:0] c,
                                          input logic       b);
    logic x;
    begin
      x = b ^ c[4];
      crc5_step = {c[3:0], 1'b0};
      if (x) crc5_step = crc5_step ^ CRC5_POLY;
    end
  endfunction

  function automatic logic [4:0] crc5_of(input logic [10:0] d);
    logic [4:0] c;
    int         i;
    begin
      c = CRC5_INIT;
      for (i = 0; i < 11; i = i + 1) c = crc5_step(c, d[i]);
      crc5_of = ~c;
    end
  endfunction

  // ---- The RESIDUAL: the same register, fed the CRC as well. ----
  //
  // The received CRC goes in MSB first, because that is the order it
  // arrives in relative to the shift register's own direction.
  function automatic logic [4:0] crc5_residual_of(input logic [10:0] d,
                                                 input logic [4:0]  r);
    logic [4:0] c;
    int         i;
    begin
      c = CRC5_INIT;
      for (i = 0; i < 11; i = i + 1) c = crc5_step(c, d[i]);
      for (i = 4; i >= 0; i = i - 1) c = crc5_step(c, r[i]);
      crc5_residual_of = c;
    end
  endfunction

  function automatic logic [15:0] crc16_step(input logic [15:0] c,
                                            input logic        b);
    logic x;
    begin
      x = b ^ c[15];
      crc16_step = {c[14:0], 1'b0};
      if (x) crc16_step = crc16_step ^ CRC16_POLY;
    end
  endfunction

  function automatic logic [15:0] crc16_byte(input logic [15:0] c,
                                            input logic [7:0]  b);
    logic [15:0] t;
    int          i;
    begin
      t = c;
      for (i = 0; i < 8; i = i + 1) t = crc16_step(t, b[i]);
      crc16_byte = t;
    end
  endfunction

  function automatic logic [15:0] crc16_absorb16(input logic [15:0] c,
                                                input logic [15:0] r);
    logic [15:0] t;
    int          i;
    begin
      t = c;
      for (i = 15; i >= 0; i = i - 1) t = crc16_step(t, r[i]);
      crc16_absorb16 = t;
    end
  endfunction

  // ---- The token check is purely combinational: 11 bits, one cycle. ----
  logic [4:0] t_gen; assign t_gen   = crc5_of(tok_data);
  logic [4:0] t_resid; assign t_resid = crc5_residual_of(tok_data, tok_crc5);
  logic t_ok; assign t_ok    = (t_resid == CRC5_RESID);

  assign crc5_gen   = t_gen;
  assign crc5_resid = t_resid;
  assign crc5_ok    = t_ok;

  // ---- The data check accumulates across the packet. ----
  logic [15:0] c16_r;
  logic [15:0] d_gen; assign d_gen   = ~c16_r;
  logic [15:0] d_resid; assign d_resid = crc16_absorb16(c16_r, dat_crc16);
  logic d_ok; assign d_ok    = (d_resid == CRC16_RESID);

  assign crc16_gen   = d_gen;
  assign crc16_resid = d_resid;
  assign crc16_ok    = d_ok;

  logic [7:0] run_r;
  logic       fp_r;
  field_e     ff_r;
  assign fail_pulse = fp_r;
  assign fail_field = ff_r;
  assign fail_run   = run_r;

  // ---- The verdict. ----
  //
  // A ratio, not a count. "Two thousand CRC errors" says nothing; "every
  // one of them upstream" says the downstream half is fine and halves the
  // search on the first reading.
  //
  // Written as a function rather than a chain of ternaries: a conditional
  // expression whose arms are enumeration literals needs an explicit cast
  // in SystemVerilog, and Icarus rejects it outright.
  function automatic verdict_e classify(input logic [31:0] dn,
                                        input logic [31:0] up);
    begin
      if ((dn == 32'd0) && (up == 32'd0)) classify = V_CLEAN;
      else if (dn > (up << 1))            classify = V_DOWN;
      else if (up > (dn << 1))            classify = V_UP;
      else                                classify = V_COMMON;
    end
  endfunction

  assign verdict = classify(n_down_fail, n_up_fail);

  // Most failures arrived in runs -> something periodic. Scattered ->
  // marginal signal. Same total, different thing to go and look at.
  assign bursty = (n_burst > n_isolated);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c16_r      <= CRC16_INIT;
      run_r      <= 8'd0;
      fp_r       <= 1'b0;
      ff_r       <= F_TOKEN;
      n_tok      <= 32'd0;
      n_tok_fail <= 32'd0;
      n_dat      <= 32'd0;
      n_dat_fail <= 32'd0;
      n_down_fail<= 32'd0;
      n_up_fail  <= 32'd0;
      n_burst    <= 32'd0;
      n_isolated <= 32'd0;
    end else begin
      fp_r <= 1'b0;

      if (eot) begin
        // Nothing: the counters are the report.
      end else begin
        // ---- the data packet's shift register ----
        //
        // dat_start and dat_valid can be asserted together, so the reset to
        // CRC16_INIT has to happen before the byte is absorbed rather than
        // instead of it. Written the other way round, the first byte of
        // every packet is silently dropped and the CRC is wrong for every
        // packet in exactly the same way -- which looks like a polynomial
        // error and is not.
        if (dat_start && dat_valid)
          c16_r <= crc16_byte(CRC16_INIT, dat_byte);
        else if (dat_start)
          c16_r <= CRC16_INIT;
        else if (dat_valid)
          c16_r <= crc16_byte(c16_r, dat_byte);

        if (tok_valid) begin
          n_tok <= n_tok + 32'd1;
          if (!t_ok) begin
            fp_r       <= 1'b1;
            ff_r       <= F_TOKEN;
            n_tok_fail <= n_tok_fail + 32'd1;
            // A TOKEN is always host-to-device. There is no ambiguity to
            // resolve and no direction input to get wrong.
            n_down_fail<= n_down_fail + 32'd1;
            if (run_r >= BURST_THRESH) n_burst    <= n_burst    + 32'd1;
            else                       n_isolated <= n_isolated + 32'd1;
            if (run_r != 8'hFF) run_r <= run_r + 8'd1;
          end else begin
            run_r <= 8'd0;
          end
        end else if (dat_end) begin
          n_dat <= n_dat + 32'd1;
          if (!d_ok) begin
            fp_r       <= 1'b1;
            ff_r       <= F_DATA;
            n_dat_fail <= n_dat_fail + 32'd1;
            // Data goes in whichever direction the transfer does, so here
            // the direction has to be told to us.
            if (dat_dir) n_up_fail   <= n_up_fail   + 32'd1;
            else         n_down_fail <= n_down_fail + 32'd1;
            if (run_r >= BURST_THRESH) n_burst    <= n_burst    + 32'd1;
            else                       n_isolated <= n_isolated + 32'd1;
            if (run_r != 8'hFF) run_r <= run_r + 8'd1;
          end else begin
            run_r <= 8'd0;
          end
        end
      end
    end
  end
endmodule

8. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb_crc_analyzer -- the two real USB CRCs, and the reason the count of
-- CRC failures is nearly useless while the DISTRIBUTION of them is the most
-- direct evidence about the physical layer you will ever get.
--
-- THE TWO POLYNOMIALS, AND WHAT EACH ONE COVERS
--
--     CRC5    x^5 + x^2 + 1                          poly 0x05
--             covers the 11-bit TOKEN field: 7-bit address + 4-bit
--             endpoint. Sent by the HOST, every single transaction.
--
--     CRC16   x^16 + x^15 + x^2 + 1                  poly 0x8005
--             covers the DATA payload. Sent by whoever has the data, so
--             either direction.
--
-- Both are initialised to all ones and both have their result complemented,
-- which is not decoration: it makes a codeword of all zeros -- the thing a
-- dead receiver produces -- fail, and it makes leading zeros in the data
-- change the result.
--
-- THE FIELD NAMES THE DIRECTION
--
-- A token is ALWAYS host-to-device. So:
--
--     CRC5 failures        the DOWNSTREAM half is damaged. Always.
--     CRC16 on IN data     the UPSTREAM half is damaged.
--     CRC16 on OUT data    the DOWNSTREAM half is damaged.
--     both halves          common mode: power, ground, the connector,
--                          or the cable as a whole.
--
-- A device reporting thousands of CRC16 failures on IN transfers and no
-- CRC5 failures at all has a perfectly good downstream path -- which rules
-- out half the usual suspects on the first reading of the counter.
--
-- AND THE CLUSTERING NAMES THE MECHANISM
--
--     scattered single failures   marginal signal. Thermal noise, an eye
--                                 that is closing, a bad margin.
--
--     failures in RUNS            something periodic. EMI from a switching
--                                 supply, a fan, a motor, a clock spur.
--
-- Same total, completely different thing to go and look at. So a failure is
-- counted BURST when the previous checked codeword also failed and ISOLATED
-- otherwise: a run of five is one isolated and four burst, and the ratio is
-- the measurement.
--
-- A RECEIVER CHECKS THE RESIDUAL, IT DOES NOT RECOMPUTE AND COMPARE
--
-- Feeding the WHOLE codeword -- data and the received CRC together --
-- through the same shift register leaves a CONSTANT, whatever the data was.
--
--     CRC5  residual = 0x0C     for all 2048 token values
--     CRC16 residual = 0x800D
--
-- Those constants are checked exhaustively by the testbench rather than
-- quoted, because a wrong residual constant produces a checker that passes
-- everything and nothing else in the system will notice.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb_crc_pkg is
  constant CRC5_INIT   : unsigned(4 downto 0)  := "11111";
  constant CRC5_POLY   : unsigned(4 downto 0)  := "00101";
  -- Named CRC5_RESIDUAL, not CRC5_RESID. VHDL identifiers are
  -- CASE-INSENSITIVE, so a constant called CRC5_RESID and the analyzer's
  -- output port called crc5_resid are the SAME NAME -- and inside the
  -- architecture the locally declared port wins. The error that produces
  -- points at the comparison, says "no matching operator =", and names two
  -- types neither of which is the one you declared.
  constant CRC5_RESIDUAL  : unsigned(4 downto 0)  := "01100";  -- 0x0C

  constant CRC16_INIT  : unsigned(15 downto 0) := x"FFFF";
  constant CRC16_POLY  : unsigned(15 downto 0) := x"8005";
  constant CRC16_RESIDUAL : unsigned(15 downto 0) := x"800D";

  constant V_CLEAN  : std_logic_vector(2 downto 0) := "000";
  constant V_DOWN   : std_logic_vector(2 downto 0) := "001";
  constant V_UP     : std_logic_vector(2 downto 0) := "010";
  constant V_COMMON : std_logic_vector(2 downto 0) := "011";

  -- One bit into the CRC5 shift register. Data goes in LSB first, which is
  -- the order it is sent on the wire; get this backwards and every one of
  -- the 2048 values is wrong.
  function crc5_step (c : unsigned(4 downto 0); b : std_logic)
    return unsigned;
  function crc5_of (d : unsigned(10 downto 0)) return unsigned;
  -- The RESIDUAL: the same register, fed the received CRC as well, MSB
  -- first -- the order it arrives in relative to the register's direction.
  function crc5_residual_of (d : unsigned(10 downto 0); r : unsigned(4 downto 0))
    return unsigned;

  function crc16_step (c : unsigned(15 downto 0); b : std_logic)
    return unsigned;
  function crc16_byte (c : unsigned(15 downto 0); b : std_logic_vector(7 downto 0))
    return unsigned;
  function crc16_absorb16 (c : unsigned(15 downto 0); r : unsigned(15 downto 0))
    return unsigned;
end package;

package body usb_crc_pkg is
  function crc5_step (c : unsigned(4 downto 0); b : std_logic)
    return unsigned is
    variable x : std_logic;
    variable t : unsigned(4 downto 0);
  begin
    x := b xor c(4);
    t := c(3 downto 0) & '0';
    if x = '1' then t := t xor CRC5_POLY; end if;
    return t;
  end function;

  function crc5_of (d : unsigned(10 downto 0)) return unsigned is
    variable c : unsigned(4 downto 0) := CRC5_INIT;
  begin
    for i in 0 to 10 loop
      c := crc5_step(c, d(i));
    end loop;
    return not c;
  end function;

  function crc5_residual_of (d : unsigned(10 downto 0); r : unsigned(4 downto 0))
    return unsigned is
    variable c : unsigned(4 downto 0) := CRC5_INIT;
  begin
    for i in 0 to 10 loop
      c := crc5_step(c, d(i));
    end loop;
    for i in 4 downto 0 loop
      c := crc5_step(c, r(i));
    end loop;
    return c;
  end function;

  function crc16_step (c : unsigned(15 downto 0); b : std_logic)
    return unsigned is
    variable x : std_logic;
    variable t : unsigned(15 downto 0);
  begin
    x := b xor c(15);
    t := c(14 downto 0) & '0';
    if x = '1' then t := t xor CRC16_POLY; end if;
    return t;
  end function;

  function crc16_byte (c : unsigned(15 downto 0); b : std_logic_vector(7 downto 0))
    return unsigned is
    variable t : unsigned(15 downto 0) := c;
  begin
    for i in 0 to 7 loop
      t := crc16_step(t, b(i));
    end loop;
    return t;
  end function;

  function crc16_absorb16 (c : unsigned(15 downto 0); r : unsigned(15 downto 0))
    return unsigned is
    variable t : unsigned(15 downto 0) := c;
  begin
    for i in 15 downto 0 loop
      t := crc16_step(t, r(i));
    end loop;
    return t;
  end function;
end package body;

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

entity usb_crc_analyzer is
  generic (
    BURST_MIN : integer := 2   -- a run of this length is a burst
  );
  port (
    clk       : in std_logic;
    rst_n     : in std_logic;

    tok_valid : in std_logic;
    tok_data  : in std_logic_vector(10 downto 0);
    tok_crc5  : in std_logic_vector(4 downto 0);

    dat_start : in std_logic;
    dat_valid : in std_logic;
    dat_byte  : in std_logic_vector(7 downto 0);
    dat_end   : in std_logic;
    dat_crc16 : in std_logic_vector(15 downto 0);
    dat_dir   : in std_logic;   -- 0 = OUT (downstream), 1 = IN (upstream)

    eot       : in std_logic;

    crc5_gen    : out std_logic_vector(4 downto 0);
    crc5_resid  : out std_logic_vector(4 downto 0);
    crc5_ok     : out std_logic;
    crc16_gen   : out std_logic_vector(15 downto 0);
    crc16_resid : out std_logic_vector(15 downto 0);
    crc16_ok    : out std_logic;

    fail_pulse  : out std_logic;
    fail_field  : out std_logic;   -- 0 = token (CRC5), 1 = data (CRC16)
    fail_run    : out std_logic_vector(7 downto 0);
    verdict     : out std_logic_vector(2 downto 0);
    bursty      : out std_logic;

    n_tok       : out unsigned(31 downto 0);
    n_tok_fail  : out unsigned(31 downto 0);
    n_dat       : out unsigned(31 downto 0);
    n_dat_fail  : out unsigned(31 downto 0);
    n_down_fail : out unsigned(31 downto 0);
    n_up_fail   : out unsigned(31 downto 0);
    n_burst     : out unsigned(31 downto 0);
    n_isolated  : out unsigned(31 downto 0)
  );
end entity;

architecture rtl of usb_crc_analyzer is
  constant BURST_THRESH : unsigned(7 downto 0) :=
    to_unsigned(BURST_MIN - 1, 8);

  signal c16_r  : unsigned(15 downto 0) := CRC16_INIT;
  signal run_r  : unsigned(7 downto 0)  := (others => '0');
  signal fp_r   : std_logic := '0';
  signal ff_r   : std_logic := '0';

  signal tok_c, tokf_c, dat_c, datf_c : unsigned(31 downto 0)
    := (others => '0');
  signal down_c, up_c, burst_c, iso_c : unsigned(31 downto 0)
    := (others => '0');

  -- Initialised at declaration. These feed concurrent comparisons that
  -- evaluate at time 0, before any driver has resolved them; left
  -- uninitialised they are all-U and numeric_std's "=" emits a metavalue
  -- warning on every single run. A warning that always fires is a warning
  -- nobody reads.
  signal t_gen, t_resid : unsigned(4 downto 0)  := (others => '0');
  signal t_ok           : std_logic := '0';
  signal d_resid        : unsigned(15 downto 0) := (others => '0');
  signal d_ok           : std_logic := '0';
begin
  -- ---- The token check is purely combinational: 11 bits, one cycle. ----
  t_gen   <= crc5_of(unsigned(tok_data));
  t_resid <= crc5_residual_of(unsigned(tok_data), unsigned(tok_crc5));
  t_ok    <= '1' when t_resid = CRC5_RESIDUAL else '0';

  crc5_gen   <= std_logic_vector(t_gen);
  crc5_resid <= std_logic_vector(t_resid);
  crc5_ok    <= t_ok;

  -- ---- The data check accumulates across the packet. ----
  d_resid <= crc16_absorb16(c16_r, unsigned(dat_crc16));
  d_ok    <= '1' when d_resid = CRC16_RESIDUAL else '0';

  crc16_gen   <= std_logic_vector(not c16_r);
  crc16_resid <= std_logic_vector(d_resid);
  crc16_ok    <= d_ok;

  fail_pulse <= fp_r;
  fail_field <= ff_r;
  fail_run   <= std_logic_vector(run_r);

  n_tok       <= tok_c;
  n_tok_fail  <= tokf_c;
  n_dat       <= dat_c;
  n_dat_fail  <= datf_c;
  n_down_fail <= down_c;
  n_up_fail   <= up_c;
  n_burst     <= burst_c;
  n_isolated  <= iso_c;

  -- ---- The verdict. ----
  --
  -- A ratio, not a count. "Two thousand CRC errors" says nothing; "every one
  -- of them upstream" says the downstream half is fine and halves the search
  -- on the first reading.
  verdict <= V_CLEAN  when (down_c = 0) and (up_c = 0) else
             V_DOWN   when down_c > (up_c sll 1) else
             V_UP     when up_c > (down_c sll 1) else
             V_COMMON;

  -- Most failures in runs -> something periodic. Scattered -> marginal
  -- signal. Same total, different thing to go and look at.
  bursty <= '1' when burst_c > iso_c else '0';

  process (clk, rst_n)
  begin
    if rst_n = '0' then
      c16_r   <= CRC16_INIT;
      run_r   <= (others => '0');
      fp_r    <= '0';
      ff_r    <= '0';
      tok_c   <= (others => '0');
      tokf_c  <= (others => '0');
      dat_c   <= (others => '0');
      datf_c  <= (others => '0');
      down_c  <= (others => '0');
      up_c    <= (others => '0');
      burst_c <= (others => '0');
      iso_c   <= (others => '0');
    elsif rising_edge(clk) then
      fp_r <= '0';

      if eot = '1' then
        -- Nothing: the counters are the report.
        null;
      else
        -- ---- the data packet's shift register ----
        --
        -- dat_start and dat_valid can be asserted together, so the reset to
        -- CRC16_INIT has to happen BEFORE the byte is absorbed rather than
        -- instead of it. Written the other way round, the first byte of
        -- every packet is silently dropped and the CRC is wrong for every
        -- packet in exactly the same way -- which looks like a polynomial
        -- error and is not.
        if dat_start = '1' and dat_valid = '1' then
          c16_r <= crc16_byte(CRC16_INIT, dat_byte);
        elsif dat_start = '1' then
          c16_r <= CRC16_INIT;
        elsif dat_valid = '1' then
          c16_r <= crc16_byte(c16_r, dat_byte);
        end if;

        if tok_valid = '1' then
          tok_c <= tok_c + 1;
          if t_ok = '0' then
            fp_r   <= '1';
            ff_r   <= '0';
            tokf_c <= tokf_c + 1;
            -- A TOKEN is always host-to-device. There is no ambiguity to
            -- resolve and no direction input to get wrong.
            down_c <= down_c + 1;
            if run_r >= BURST_THRESH then
              burst_c <= burst_c + 1;
            else
              iso_c <= iso_c + 1;
            end if;
            if run_r /= x"FF" then run_r <= run_r + 1; end if;
          else
            run_r <= (others => '0');
          end if;
        elsif dat_end = '1' then
          dat_c <= dat_c + 1;
          if d_ok = '0' then
            fp_r   <= '1';
            ff_r   <= '1';
            datf_c <= datf_c + 1;
            -- Data goes whichever way the transfer does, so here the
            -- direction has to be told to us.
            if dat_dir = '1' then
              up_c <= up_c + 1;
            else
              down_c <= down_c + 1;
            end if;
            if run_r >= BURST_THRESH then
              burst_c <= burst_c + 1;
            else
              iso_c <= iso_c + 1;
            end if;
            if run_r /= x"FF" then run_r <= run_r + 1; end if;
          else
            run_r <= (others => '0');
          end if;
        end if;
      end if;
    end if;
  end process;
end architecture;

9. Seeing the Residual

A good token and a corrupted one

usb_crc_analyzer — residual checking on a token

10 cycles
A ten-cycle waveform. On the first cycle a token with value 0x715 and its correct CRC5 of 0x17 is presented; the residual output reads 0x0C and the ok output is high. On the third cycle the same token is presented with the CRC5 corrupted to 0x16; the residual reads a different value, ok goes low, the fail pulse goes high, and the downstream failure counter advances from zero to one while the upstream counter stays at zero.correct: residual is the constant 0x0Ccorrect: residual is theconstant 0x0Cone CRC bit flipped: residual differsone CRC bit flipped:residual differsdownstream, with no direction inputdownstream, with nodirection inputclktok_validtok_data715715715715715715715715715715tok_crc517171616161616161616crc5_resid0C0C1A1A1A1A1A1A1A1Acrc5_okfail_pulsen_down_fail0001111111n_up_fail0000000000t0t1t2t3t4t5t6t7t8t9
The same 11-bit token field is driven twice: first with its correct CRC5, then with one bit of the CRC flipped. The residual is the constant 0x0C in the first case and something else in the second — the checker never compares against a computed value, only against that constant. The failure is attributed downstream without consulting any direction input, because a token only ever travels one way.

And the measurement that separates a marginal rail from an interference source:

Five failures in a run, and five failures spread out

usb_crc_analyzer — burst versus isolated

10 cycles
A ten-cycle waveform showing token checks. The first four cycles are consecutive failures: the failure run counter climbs from zero to four, the isolated counter advances once on the first and the burst counter advances on the three that follow. The remaining cycles alternate a good codeword with a failing one, so the run counter returns to zero after each good one and every failure is counted isolated, advancing the isolated counter without touching the burst counter.a run: one isolated, then bursta run: one isolated, thenbursta good codeword resets the runa good codeword resets therunseparated failures: all isolatedseparated failures: allisolatedsame total, different diagnosissame total, differentdiagnosisclktok_validcrc5_okfail_run0123401010n_isolated0111111223n_burst0012333333burstyt0t1t2t3t4t5t6t7t8t9
Both halves of this window contain failures. In a run, only the first is ISOLATED and the rest are BURST; separated by good codewords, every one is isolated. The totals are what a conventional counter reports and they are identical; the split is the part that says which fault to go looking for.

10. The Testbenches

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
// Testbench for usb_crc_analyzer.
//
// THE ORACLE IS A BIT-SERIAL REFERENCE, WRITTEN INDEPENDENTLY
//
// A CRC is the one block where "the model agrees with the design" is worth
// almost nothing, because both were probably derived from the same
// half-remembered table. So the reference here is anchored to something
// outside both: the USB 2.0 specification's own worked example --
//
//     address 0x15, endpoint 0xE   ->   CRC5 = 0x17
//
// -- which is checked first, before anything else runs. If that one value
// is wrong, every other number in this file is a consistent lie.
//
// THE EXHAUSTIVE CLAIMS
//
//   1. all 2048 token values: the generated CRC5 matches the reference
//   2. all 2048 token values: the residual of the correct codeword is the
//      SAME CONSTANT (this is what makes residual checking legal at all)
//   3. all 2048 x 16 = 32768 single-bit errors are DETECTED
//   4. all 2048 x 120 = 245760 double-bit errors are DETECTED
//
// The last two are the properties the polynomial was chosen for, and they
// are the only ones that matter: a CRC that computes a repeatable value and
// detects nothing is a checksum with extra steps.
module tb_ca_v;

  localparam integer BURST_MIN = 2;

  localparam [4:0]  CRC5_RESID  = 5'h0C;
  localparam [15:0] CRC16_RESID = 16'h800D;

  localparam [2:0] V_CLEAN = 3'd0, V_DOWN = 3'd1, V_UP = 3'd2, V_COMMON = 3'd3;

  reg         clk = 1'b0, rst_n = 1'b0;
  reg         tok_valid = 1'b0;
  reg  [10:0] tok_data = 11'd0;
  reg  [4:0]  tok_crc5 = 5'd0;
  reg         dat_start = 1'b0, dat_valid = 1'b0, dat_end = 1'b0;
  reg  [7:0]  dat_byte = 8'd0;
  reg  [15:0] dat_crc16 = 16'd0;
  reg         dat_dir = 1'b0;
  reg         eot = 1'b0;

  wire [4:0]  crc5_gen, crc5_resid;
  wire        crc5_ok;
  wire [15:0] crc16_gen, crc16_resid;
  wire        crc16_ok;
  wire        fail_pulse, fail_field, bursty;
  wire [7:0]  fail_run;
  wire [2:0]  verdict;
  wire [31:0] n_tok, n_tok_fail, n_dat, n_dat_fail;
  wire [31:0] n_down_fail, n_up_fail, n_burst, n_isolated;

  usb_crc_analyzer #(.BURST_MIN(BURST_MIN)) dut (
    .clk(clk), .rst_n(rst_n),
    .tok_valid(tok_valid), .tok_data(tok_data), .tok_crc5(tok_crc5),
    .dat_start(dat_start), .dat_valid(dat_valid), .dat_byte(dat_byte),
    .dat_end(dat_end), .dat_crc16(dat_crc16), .dat_dir(dat_dir),
    .eot(eot),
    .crc5_gen(crc5_gen), .crc5_resid(crc5_resid), .crc5_ok(crc5_ok),
    .crc16_gen(crc16_gen), .crc16_resid(crc16_resid), .crc16_ok(crc16_ok),
    .fail_pulse(fail_pulse), .fail_field(fail_field), .fail_run(fail_run),
    .verdict(verdict), .bursty(bursty),
    .n_tok(n_tok), .n_tok_fail(n_tok_fail),
    .n_dat(n_dat), .n_dat_fail(n_dat_fail),
    .n_down_fail(n_down_fail), .n_up_fail(n_up_fail),
    .n_burst(n_burst), .n_isolated(n_isolated)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0, steps = 0;

  // ---- a shadow model of the CLASSIFICATION ----
  //
  // The phases below prove the CRC arithmetic exhaustively, which is the
  // hard part and not the whole part: the block also has to decide which
  // HALF of the link a failure belongs to and whether it arrived in a run.
  // Those two decisions were originally checked only by the six directed
  // assertions in phases 5 to 7 -- so the mutations that broke them scored
  // 3 and 2 against scores in the thousands for everything else.
  //
  // A mutation that survives on a handful of checks has not really been
  // killed; it has been cornered. So every event now predicts all of it.
  integer e_down, e_up, e_burst, e_iso, e_run, e_tokfail, e_datfail;

  task ck;
    input [255:0] nm;
    input [31:0]  got, exp;
    begin
      checks = checks + 1;
      if (got !== exp) begin
        errors = errors + 1;
        if (errors < 25)
          $display("FAIL t=%0t step=%0d %0s got=%0h exp=%0h",
                   $time, steps, nm, got, exp);
      end
    end
  endtask

  // ---------------- the independent bit-serial reference ----------------
  //
  // Written as a loop over single bits rather than as a table or an
  // unrolled XOR tree, because the loop IS the definition of the
  // polynomial division and the other two forms are optimisations of it
  // that have to be checked against something.
  function [4:0] ref_crc5;
    input [10:0] d;
    reg [4:0] c;
    reg       x;
    integer   i;
    begin
      c = 5'h1F;
      for (i = 0; i < 11; i = i + 1) begin
        x = d[i] ^ c[4];
        c = {c[3:0], 1'b0};
        if (x) c = c ^ 5'h05;
      end
      ref_crc5 = ~c;
    end
  endfunction

  function [4:0] ref_crc5_resid;
    input [10:0] d;
    input [4:0]  r;
    reg [4:0] c;
    reg       x;
    integer   i;
    begin
      c = 5'h1F;
      for (i = 0; i < 11; i = i + 1) begin
        x = d[i] ^ c[4];
        c = {c[3:0], 1'b0};
        if (x) c = c ^ 5'h05;
      end
      for (i = 4; i >= 0; i = i - 1) begin
        x = r[i] ^ c[4];
        c = {c[3:0], 1'b0};
        if (x) c = c ^ 5'h05;
      end
      ref_crc5_resid = c;
    end
  endfunction

  function [15:0] ref_crc16_byte;
    input [15:0] c0;
    input [7:0]  b;
    reg [15:0] c;
    reg        x;
    integer    i;
    begin
      c = c0;
      for (i = 0; i < 8; i = i + 1) begin
        x = b[i] ^ c[15];
        c = {c[14:0], 1'b0};
        if (x) c = c ^ 16'h8005;
      end
      ref_crc16_byte = c;
    end
  endfunction

  // Called on the cycle the design consumes an event. `ok` is computed from
  // the reference, never read back from the design.
  task classify; input ok; input is_data; input dir;
    begin
      if (!ok) begin
        if (is_data) begin
          e_datfail = e_datfail + 1;
          if (dir) e_up = e_up + 1; else e_down = e_down + 1;
        end else begin
          e_tokfail = e_tokfail + 1;
          // A token is ALWAYS host-to-device. The model asserts that as a
          // fact about the protocol rather than reading a direction input.
          e_down = e_down + 1;
        end
        if (e_run >= 1) e_burst = e_burst + 1;
        else            e_iso   = e_iso   + 1;
        if (e_run != 255) e_run = e_run + 1;
      end else begin
        e_run = 0;
      end
      ck("n_tok_fail",  n_tok_fail,  e_tokfail);
      ck("n_dat_fail",  n_dat_fail,  e_datfail);
      ck("n_down_fail", n_down_fail, e_down);
      ck("n_up_fail",   n_up_fail,   e_up);
      ck("n_burst",     n_burst,     e_burst);
      ck("n_isolated",  n_isolated,  e_iso);
      ck("fail_run",    {24'd0, fail_run}, e_run);
      // Every failure is exactly one of burst or isolated, and exactly one
      // of upstream or downstream. A report that cannot be decomposed
      // cannot be trusted (chapter 23.4).
      ck("burst+iso",   n_burst + n_isolated,  n_tok_fail + n_dat_fail);
      ck("up+down",     n_down_fail + n_up_fail, n_tok_fail + n_dat_fail);
    end
  endtask

  task clear_expect;
    begin
      e_down = 0; e_up = 0; e_burst = 0; e_iso = 0; e_run = 0;
      e_tokfail = 0; e_datfail = 0;
    end
  endtask

  // ---------------- drivers ----------------
  task tok; input [10:0] d; input [4:0] c;
    begin
      tok_valid = 1'b1; tok_data = d; tok_crc5 = c;
      dat_start = 1'b0; dat_valid = 1'b0; dat_end = 1'b0; eot = 1'b0;
      @(posedge clk); #1;
      steps = steps + 1;
      classify(ref_crc5_resid(d, c) == CRC5_RESID, 1'b0, 1'b0);
      tok_valid = 1'b0;
    end
  endtask

  task idle;
    begin
      tok_valid = 1'b0; dat_start = 1'b0; dat_valid = 1'b0; dat_end = 1'b0;
      eot = 1'b0;
      @(posedge clk); #1;
      steps = steps + 1;
    end
  endtask

  // A whole data packet: start, n bytes, end. The packet's CRC16 is passed
  // in rather than computed here, so a test can hand over a WRONG one.
  reg [7:0] pay [0:63];
  task datpkt; input integer n; input [15:0] c16; input dir;
    integer j;
    begin
      dat_dir = dir;
      tok_valid = 1'b0; eot = 1'b0;
      if (n == 0) begin
        // A zero-length packet is a real packet with a real CRC16. It is
        // also the one length nobody tests.
        dat_start = 1'b1; dat_valid = 1'b0; dat_end = 1'b0;
        @(posedge clk); #1; steps = steps + 1;
      end else begin
        for (j = 0; j < n; j = j + 1) begin
          dat_start = (j == 0);
          dat_valid = 1'b1;
          dat_byte  = pay[j];
          dat_end   = 1'b0;
          @(posedge clk); #1; steps = steps + 1;
        end
      end
      dat_start = 1'b0; dat_valid = 1'b0;
      dat_end   = 1'b1; dat_crc16 = c16;
      @(posedge clk); #1; steps = steps + 1;
      classify(c16 == pay_crc16(n), 1'b1, dir);
      dat_end = 1'b0;
    end
  endtask

  function [15:0] pay_crc16; input integer n;
    reg [15:0] c;
    integer j;
    begin
      c = 16'hFFFF;
      for (j = 0; j < n; j = j + 1) c = ref_crc16_byte(c, pay[j]);
      pay_crc16 = ~c;
    end
  endfunction

  integer i, j, bi, bj, n, w;
  integer n_single, n_double, n_miss_s, n_miss_d;
  integer base_burst, base_iso, base_tf, base_df, base_up, base_down;
  reg [15:0] cw, bad;
  reg [4:0]  g;
  reg [15:0] c16;
  reg [0:0]  seen_resid [0:31];
  integer    n_resid;

  initial begin
    for (i = 0; i < 32; i = i + 1) seen_resid[i] = 1'b0;
    clear_expect;

    repeat (3) @(posedge clk);
    rst_n = 1'b1;
    @(negedge clk);

    // ================= PHASE 0 -- the specification's own example ========
    //
    // Before anything else. Address 0x15, endpoint 0xE, CRC5 = 0x17, from
    // the USB 2.0 specification. Every other number in this file depends on
    // this one being right, and nothing inside this testbench could tell
    // you if it were not.
    tok(11'h715, ref_crc5(11'h715));
    ck("spec example CRC5", {27'd0, crc5_gen}, 32'h17);
    ck("spec example ref",  {27'd0, ref_crc5(11'h715)}, 32'h17);
    ck("spec example ok",   {31'd0, crc5_ok}, 32'd1);

    // ================= PHASE 1 -- all 2048 tokens, generated and checked ==
    n_resid = 0;
    for (i = 0; i < 2048; i = i + 1) begin
      g = ref_crc5(i[10:0]);
      tok(i[10:0], g);
      ck("crc5_gen",   {27'd0, crc5_gen},   {27'd0, g});
      ck("crc5_ok",    {31'd0, crc5_ok},    32'd1);
      ck("crc5_resid", {27'd0, crc5_resid}, {27'd0, CRC5_RESID});
      // ...and record the residual actually observed, so that the claim
      // "it is a constant" is MEASURED rather than assumed.
      if (!seen_resid[crc5_resid]) begin
        seen_resid[crc5_resid] = 1'b1;
        n_resid = n_resid + 1;
      end
    end
    if (n_resid != 1) begin
      errors = errors + 1;
      $display("FAIL the CRC5 residual is not a constant: %0d values seen",
               n_resid);
    end

    // ================= PHASE 2 -- every single-bit error, all 2048 =======
    //
    // 2048 x 16 = 32768 injections. The polynomial detects all of them or
    // it is the wrong polynomial.
    n_single = 0; n_miss_s = 0;
    base_tf = n_tok_fail;
    for (i = 0; i < 2048; i = i + 1) begin
      // ---- A CORRECT codeword first, every time. ----
      //
      // Without this the phase drives tens of thousands of CONSECUTIVE
      // failures and never once follows a failure with a success -- which
      // is the only transition the run-reset logic has. The mutation that
      // deletes that reset scored 51 against scores in the millions, not
      // because the check was weak but because the stimulus never produced
      // the situation it checks.
      tok(i[10:0], ref_crc5(i[10:0]));
      cw = {5'd0, i[10:0]} | ({11'd0, ref_crc5(i[10:0])} << 11);
      for (bi = 0; bi < 16; bi = bi + 1) begin
        bad = cw ^ (16'd1 << bi);
        tok(bad[10:0], bad[15:11]);
        n_single = n_single + 1;
        checks   = checks + 1;
        if (crc5_ok) begin
          n_miss_s = n_miss_s + 1;
          if (n_miss_s < 5)
            $display("FAIL single-bit error UNDETECTED token=%0h bit=%0d",
                     i, bi);
        end
      end
    end
    if (n_miss_s != 0) errors = errors + 1;
    // Every one of them is also a reported downstream failure, because a
    // token is always host-to-device.
    ck("single-bit failures counted", n_tok_fail - base_tf, n_single);

    // ================= PHASE 3 -- every double-bit error, all 2048 =======
    //
    // 2048 x 120 = 245760 injections. This is the property that separates a
    // CRC from a parity bit.
    n_double = 0; n_miss_d = 0;
    for (i = 0; i < 2048; i = i + 1) begin
      // ...and again here, for the same reason.
      tok(i[10:0], ref_crc5(i[10:0]));
      cw = {5'd0, i[10:0]} | ({11'd0, ref_crc5(i[10:0])} << 11);
      for (bi = 0; bi < 16; bi = bi + 1)
        for (bj = bi + 1; bj < 16; bj = bj + 1) begin
          bad = cw ^ (16'd1 << bi) ^ (16'd1 << bj);
          tok(bad[10:0], bad[15:11]);
          n_double = n_double + 1;
          checks   = checks + 1;
          if (crc5_ok) begin
            n_miss_d = n_miss_d + 1;
            if (n_miss_d < 5)
              $display("FAIL double-bit error UNDETECTED token=%0h %0d,%0d",
                       i, bi, bj);
          end
        end
    end
    if (n_miss_d != 0) errors = errors + 1;

    // ================= PHASE 4 -- CRC16 over real packets ================
    //
    // Including the zero-length packet, which is a real packet with a real
    // CRC16 and the one length nobody tests.
    for (n = 0; n <= 16; n = n + 1) begin
      for (j = 0; j < n; j = j + 1) pay[j] = ($unsigned($random) % 256);
      c16 = pay_crc16(n);
      idle;
      datpkt(n, c16, 1'b0);
      ck("crc16_ok",    {31'd0, crc16_ok},    32'd1);
      ck("crc16_gen",   {16'd0, crc16_gen},   {16'd0, c16});
      ck("crc16_resid", {16'd0, crc16_resid}, {16'd0, CRC16_RESID});
    end

    // ================= PHASE 5 -- CRC16 single-bit detection =============
    //
    // Every bit of an 8-byte payload plus its 16-bit CRC: 80 positions,
    // over 64 different payloads. Not exhaustive over payloads -- 2^64 is
    // not a testbench -- but exhaustive over ERROR POSITION, which is the
    // axis the polynomial makes a promise about.
    base_df = n_dat_fail;
    n_single = 0;
    for (i = 0; i < 64; i = i + 1) begin
      for (j = 0; j < 8; j = j + 1) pay[j] = ($unsigned($random) % 256);
      c16 = pay_crc16(8);
      for (bi = 0; bi < 80; bi = bi + 1) begin
        // flip one bit: in the payload if bi < 64, in the CRC otherwise
        if (bi < 64) pay[bi/8] = pay[bi/8] ^ (8'd1 << (bi % 8));
        idle;
        datpkt(8, (bi >= 64) ? (c16 ^ (16'd1 << (bi - 64))) : c16, 1'b1);
        n_single = n_single + 1;
        checks   = checks + 1;
        if (crc16_ok) begin
          errors = errors + 1;
          if (errors < 25)
            $display("FAIL CRC16 single-bit error UNDETECTED payload=%0d bit=%0d",
                     i, bi);
        end
        if (bi < 64) pay[bi/8] = pay[bi/8] ^ (8'd1 << (bi % 8));
      end
    end
    ck("crc16 single-bit failures counted", n_dat_fail - base_df, n_single);
    // ...and every one of them was IN data, so every one is UPSTREAM. This
    // is the check that makes the verdict mean something: the direction is
    // not inferred from the failure, it is carried by the transaction.
    ck("all upstream", n_up_fail, n_single);

    // ================= PHASE 6 -- burst versus isolated ==================
    //
    // The measurement that separates marginal signal from interference. A
    // run of five failures is ONE isolated and FOUR burst; five failures
    // each separated by a good one are FIVE isolated.
    idle;
    tok(11'h123, ref_crc5(11'h123));     // a good one, to clear the run
    base_burst = n_burst; base_iso = n_isolated;
    for (i = 0; i < 5; i = i + 1) tok(11'h123, ref_crc5(11'h123) ^ 5'h01);
    if (n_burst - base_burst != 4 || n_isolated - base_iso != 1) begin
      errors = errors + 1;
      $display("FAIL run of 5: burst=%0d isolated=%0d expected 4 and 1",
               n_burst - base_burst, n_isolated - base_iso);
    end
    tok(11'h123, ref_crc5(11'h123));     // clear the run
    base_burst = n_burst; base_iso = n_isolated;
    for (i = 0; i < 5; i = i + 1) begin
      tok(11'h123, ref_crc5(11'h123) ^ 5'h01);
      tok(11'h123, ref_crc5(11'h123));
    end
    if (n_burst - base_burst != 0 || n_isolated - base_iso != 5) begin
      errors = errors + 1;
      $display("FAIL 5 separated: burst=%0d isolated=%0d expected 0 and 5",
               n_burst - base_burst, n_isolated - base_iso);
    end

    // ================= PHASE 7 -- the verdict ============================
    //
    // Three runs from a clean reset, each with the failures on one side of
    // the link, and the verdict has to name the right half.
    //
    // Reset between them, because a verdict is a RATIO over accumulated
    // counters: computed on top of the previous phase's totals it is a
    // ratio of the wrong things (chapter 25.4 phase 4, in a new costume).
    reset_dut;
    for (i = 0; i < 40; i = i + 1) tok(11'h055, ref_crc5(11'h055) ^ 5'h03);
    ck("verdict downstream", {29'd0, verdict}, {29'd0, V_DOWN});

    reset_dut;
    for (j = 0; j < 8; j = j + 1) pay[j] = j[7:0];
    c16 = pay_crc16(8);
    for (i = 0; i < 40; i = i + 1) begin
      idle;
      datpkt(8, c16 ^ 16'h0001, 1'b1);       // IN data, wrong CRC
    end
    ck("verdict upstream", {29'd0, verdict}, {29'd0, V_UP});

    reset_dut;
    for (i = 0; i < 20; i = i + 1) begin
      tok(11'h055, ref_crc5(11'h055) ^ 5'h03);
      idle;
      datpkt(8, c16 ^ 16'h0001, 1'b1);
    end
    ck("verdict common", {29'd0, verdict}, {29'd0, V_COMMON});

    reset_dut;
    for (i = 0; i < 20; i = i + 1) tok(11'h055, ref_crc5(11'h055));
    ck("verdict clean", {29'd0, verdict}, {29'd0, V_CLEAN});

    $display("steps=%0d checks=%0d errors=%0d", steps, checks, errors);
    $display("single-bit injections=%0d undetected=%0d", 2048*16, n_miss_s);
    $display("double-bit injections=%0d undetected=%0d", 2048*120, n_miss_d);
    $display("crc5 residual values observed=%0d (expected 1)", n_resid);
    $display("%0s: %0d errors in %0d checks",
             (errors == 0) ? "PASS" : "FAIL", errors, checks);
    $finish;
  end

  task reset_dut;
    begin
      tok_valid = 1'b0; dat_start = 1'b0; dat_valid = 1'b0; dat_end = 1'b0;
      rst_n = 1'b0;
      @(posedge clk); #1;
      rst_n = 1'b1;
      @(negedge clk);
      // The DUT's counters went to zero, so the expectations must too --
      // otherwise every check after the first reset fails for a reason that
      // has nothing to do with the design.
      clear_expect;
    end
  endtask
endmodule

SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
// Testbench for usb_crc_analyzer.
//
// THE ORACLE IS A BIT-SERIAL REFERENCE, WRITTEN INDEPENDENTLY
//
// A CRC is the one block where "the model agrees with the design" is worth
// almost nothing, because both were probably derived from the same
// half-remembered table. So the reference here is anchored to something
// outside both: the USB 2.0 specification's own worked example --
//
//     address 0x15, endpoint 0xE   ->   CRC5 = 0x17
//
// -- which is checked first, before anything else runs. If that one value
// is wrong, every other number in this file is a consistent lie.
//
// THE EXHAUSTIVE CLAIMS
//
//   1. all 2048 token values: the generated CRC5 matches the reference
//   2. all 2048 token values: the residual of the correct codeword is the
//      SAME CONSTANT (this is what makes residual checking legal at all)
//   3. all 2048 x 16 = 32768 single-bit errors are DETECTED
//   4. all 2048 x 120 = 245760 double-bit errors are DETECTED
//
// The last two are the properties the polynomial was chosen for, and they
// are the only ones that matter: a CRC that computes a repeatable value and
// detects nothing is a checksum with extra steps.
module tb_ca_sv;
  import usb_crc_pkg::*;

  localparam int BURST_MIN = 2;

  localparam [4:0]  CRC5_RESID  = 5'h0C;
  localparam [15:0] CRC16_RESID = 16'h800D;

  logic       clk = 1'b0, rst_n = 1'b0;
  logic       tok_valid = 1'b0;
  logic [10:0] tok_data = 11'd0;
  logic [4:0]  tok_crc5 = 5'd0;
  logic       dat_start = 1'b0, dat_valid = 1'b0, dat_end = 1'b0;
  logic [7:0]  dat_byte = 8'd0;
  logic [15:0] dat_crc16 = 16'd0;
  logic       dat_dir = 1'b0;
  logic       eot = 1'b0;

  logic [4:0]  crc5_gen, crc5_resid;
  logic        crc5_ok;
  logic [15:0] crc16_gen, crc16_resid;
  logic        crc16_ok;
  logic        fail_pulse, bursty;
  field_e      fail_field;
  logic [7:0]  fail_run;
  verdict_e    verdict;
  logic [31:0] n_tok, n_tok_fail, n_dat, n_dat_fail;
  logic [31:0] n_down_fail, n_up_fail, n_burst, n_isolated;

  usb_crc_analyzer #(.BURST_MIN(BURST_MIN)) dut (
    .clk(clk), .rst_n(rst_n),
    .tok_valid(tok_valid), .tok_data(tok_data), .tok_crc5(tok_crc5),
    .dat_start(dat_start), .dat_valid(dat_valid), .dat_byte(dat_byte),
    .dat_end(dat_end), .dat_crc16(dat_crc16), .dat_dir(dat_dir),
    .eot(eot),
    .crc5_gen(crc5_gen), .crc5_resid(crc5_resid), .crc5_ok(crc5_ok),
    .crc16_gen(crc16_gen), .crc16_resid(crc16_resid), .crc16_ok(crc16_ok),
    .fail_pulse(fail_pulse), .fail_field(fail_field), .fail_run(fail_run),
    .verdict(verdict), .bursty(bursty),
    .n_tok(n_tok), .n_tok_fail(n_tok_fail),
    .n_dat(n_dat), .n_dat_fail(n_dat_fail),
    .n_down_fail(n_down_fail), .n_up_fail(n_up_fail),
    .n_burst(n_burst), .n_isolated(n_isolated)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0, steps = 0;

  // ---- a shadow model of the CLASSIFICATION ----
  //
  // The phases below prove the CRC arithmetic exhaustively, which is the
  // hard part and not the whole part: the block also has to decide which
  // HALF of the link a failure belongs to and whether it arrived in a run.
  // Those two decisions were originally checked only by the six directed
  // assertions in phases 5 to 7 -- so the mutations that broke them scored
  // 3 and 2 against scores in the thousands for everything else.
  //
  // A mutation that survives on a handful of checks has not really been
  // killed; it has been cornered. So every event now predicts all of it.
  int e_down, e_up, e_burst, e_iso, e_run, e_tokfail, e_datfail;

  task ck;
    input [255:0] nm;
    input [31:0]  got, exp;
    begin
      checks = checks + 1;
      if (got !== exp) begin
        errors = errors + 1;
        if (errors < 25)
          $display("FAIL t=%0t step=%0d %0s got=%0h exp=%0h",
                   $time, steps, nm, got, exp);
      end
    end
  endtask

  // ---------------- the independent bit-serial reference ----------------
  //
  // Written as a loop over single bits rather than as a table or an
  // unrolled XOR tree, because the loop IS the definition of the
  // polynomial division and the other two forms are optimisations of it
  // that have to be checked against something.
  function automatic logic [4:0] ref_crc5(input logic [10:0] d);
    logic [4:0] c;
    logic       x;
    int         i;
    begin
      c = 5'h1F;
      for (i = 0; i < 11; i = i + 1) begin
        x = d[i] ^ c[4];
        c = {c[3:0], 1'b0};
        if (x) c = c ^ 5'h05;
      end
      ref_crc5 = ~c;
    end
  endfunction

  function automatic logic [4:0] ref_crc5_resid(input logic [10:0] d,
                                               input logic [4:0]  r);
    logic [4:0] c;
    logic       x;
    int         i;
    begin
      c = 5'h1F;
      for (i = 0; i < 11; i = i + 1) begin
        x = d[i] ^ c[4];
        c = {c[3:0], 1'b0};
        if (x) c = c ^ 5'h05;
      end
      for (i = 4; i >= 0; i = i - 1) begin
        x = r[i] ^ c[4];
        c = {c[3:0], 1'b0};
        if (x) c = c ^ 5'h05;
      end
      ref_crc5_resid = c;
    end
  endfunction

  function automatic logic [15:0] ref_crc16_byte(input logic [15:0] c0,
                                                input logic [7:0]  b);
    logic [15:0] c;
    logic        x;
    int          i;
    begin
      c = c0;
      for (i = 0; i < 8; i = i + 1) begin
        x = b[i] ^ c[15];
        c = {c[14:0], 1'b0};
        if (x) c = c ^ 16'h8005;
      end
      ref_crc16_byte = c;
    end
  endfunction

  // Called on the cycle the design consumes an event. `ok` is computed from
  // the reference, never read back from the design.
  task automatic classify(logic ok, logic is_data, logic dir);
    begin
      if (!ok) begin
        if (is_data) begin
          e_datfail = e_datfail + 1;
          if (dir) e_up = e_up + 1; else e_down = e_down + 1;
        end else begin
          e_tokfail = e_tokfail + 1;
          // A token is ALWAYS host-to-device. The model asserts that as a
          // fact about the protocol rather than reading a direction input.
          e_down = e_down + 1;
        end
        if (e_run >= 1) e_burst = e_burst + 1;
        else            e_iso   = e_iso   + 1;
        if (e_run != 255) e_run = e_run + 1;
      end else begin
        e_run = 0;
      end
      ck("n_tok_fail",  n_tok_fail,  e_tokfail);
      ck("n_dat_fail",  n_dat_fail,  e_datfail);
      ck("n_down_fail", n_down_fail, e_down);
      ck("n_up_fail",   n_up_fail,   e_up);
      ck("n_burst",     n_burst,     e_burst);
      ck("n_isolated",  n_isolated,  e_iso);
      ck("fail_run",    fail_run, e_run);
      // Every failure is exactly one of burst or isolated, and exactly one
      // of upstream or downstream. A report that cannot be decomposed
      // cannot be trusted (chapter 23.4).
      ck("burst+iso",   n_burst + n_isolated,  n_tok_fail + n_dat_fail);
      ck("up+down",     n_down_fail + n_up_fail, n_tok_fail + n_dat_fail);
    end
  endtask

  task automatic clear_expect;
    begin
      e_down = 0; e_up = 0; e_burst = 0; e_iso = 0; e_run = 0;
      e_tokfail = 0; e_datfail = 0;
    end
  endtask

  // ---------------- drivers ----------------
  task automatic tok(logic [10:0] d, logic [4:0] c);
    begin
      tok_valid = 1'b1; tok_data = d; tok_crc5 = c;
      dat_start = 1'b0; dat_valid = 1'b0; dat_end = 1'b0; eot = 1'b0;
      @(posedge clk); #1;
      steps = steps + 1;
      classify(ref_crc5_resid(d, c) == CRC5_RESID, 1'b0, 1'b0);
      tok_valid = 1'b0;
    end
  endtask

  task automatic idle;
    begin
      tok_valid = 1'b0; dat_start = 1'b0; dat_valid = 1'b0; dat_end = 1'b0;
      eot = 1'b0;
      @(posedge clk); #1;
      steps = steps + 1;
    end
  endtask

  // A whole data packet: start, n bytes, end. The packet's CRC16 is passed
  // in rather than computed here, so a test can hand over a WRONG one.
  logic [7:0] pay [0:63];
  task automatic datpkt(int n, logic [15:0] c16, logic dir);
    int j;
    begin
      dat_dir = dir;
      tok_valid = 1'b0; eot = 1'b0;
      if (n == 0) begin
        // A zero-length packet is a real packet with a real CRC16. It is
        // also the one length nobody tests.
        dat_start = 1'b1; dat_valid = 1'b0; dat_end = 1'b0;
        @(posedge clk); #1; steps = steps + 1;
      end else begin
        for (j = 0; j < n; j = j + 1) begin
          dat_start = (j == 0);
          dat_valid = 1'b1;
          dat_byte  = pay[j];
          dat_end   = 1'b0;
          @(posedge clk); #1; steps = steps + 1;
        end
      end
      dat_start = 1'b0; dat_valid = 1'b0;
      dat_end   = 1'b1; dat_crc16 = c16;
      @(posedge clk); #1; steps = steps + 1;
      classify(c16 == pay_crc16(n), 1'b1, dir);
      dat_end = 1'b0;
    end
  endtask

  function automatic logic [15:0] pay_crc16(input int n);
    logic [15:0] c;
    int j;
    begin
      c = 16'hFFFF;
      for (j = 0; j < n; j = j + 1) c = ref_crc16_byte(c, pay[j]);
      pay_crc16 = ~c;
    end
  endfunction

  int i, j, bi, bj, n, w;
  int n_single, n_double, n_miss_s, n_miss_d;
  int base_burst, base_iso, base_tf, base_df, base_up, base_down;
  logic [15:0] cw, bad;
  logic [4:0]  g;
  logic [15:0] c16;
  bit          seen_resid [32];
  int          n_resid;

  initial begin
    foreach (seen_resid[q]) seen_resid[q] = 1'b0;
    clear_expect;

    repeat (3) @(posedge clk);
    rst_n = 1'b1;
    @(negedge clk);

    // ================= PHASE 0 -- the specification's own example ========
    //
    // Before anything else. Address 0x15, endpoint 0xE, CRC5 = 0x17, from
    // the USB 2.0 specification. Every other number in this file depends on
    // this one being right, and nothing inside this testbench could tell
    // you if it were not.
    tok(11'h715, ref_crc5(11'h715));
    ck("spec example CRC5", crc5_gen, 32'h17);
    ck("spec example ref",  ref_crc5(11'h715), 32'h17);
    ck("spec example ok",   crc5_ok, 32'd1);

    // ================= PHASE 1 -- all 2048 tokens, generated and checked ==
    n_resid = 0;
    for (i = 0; i < 2048; i = i + 1) begin
      g = ref_crc5(i[10:0]);
      tok(i[10:0], g);
      ck("crc5_gen",   crc5_gen, g);
      ck("crc5_ok",    crc5_ok, 32'd1);
      ck("crc5_resid", crc5_resid, CRC5_RESID);
      // ...and record the residual actually observed, so that the claim
      // "it is a constant" is MEASURED rather than assumed.
      if (!seen_resid[crc5_resid]) begin
        seen_resid[crc5_resid] = 1'b1;
        n_resid = n_resid + 1;
      end
    end
    if (n_resid != 1) begin
      errors = errors + 1;
      $display("FAIL the CRC5 residual is not a constant: %0d values seen",
               n_resid);
    end

    // ================= PHASE 2 -- every single-bit error, all 2048 =======
    //
    // 2048 x 16 = 32768 injections. The polynomial detects all of them or
    // it is the wrong polynomial.
    n_single = 0; n_miss_s = 0;
    base_tf = n_tok_fail;
    for (i = 0; i < 2048; i = i + 1) begin
      // ---- A CORRECT codeword first, every time. ----
      //
      // Without this the phase drives tens of thousands of CONSECUTIVE
      // failures and never once follows a failure with a success -- which
      // is the only transition the run-reset logic has. The mutation that
      // deletes that reset scored 51 against scores in the millions, not
      // because the check was weak but because the stimulus never produced
      // the situation it checks.
      tok(i[10:0], ref_crc5(i[10:0]));
      cw = {5'd0, i[10:0]} | ({11'd0, ref_crc5(i[10:0])} << 11);
      for (bi = 0; bi < 16; bi = bi + 1) begin
        bad = cw ^ (16'd1 << bi);
        tok(bad[10:0], bad[15:11]);
        n_single = n_single + 1;
        checks   = checks + 1;
        if (crc5_ok) begin
          n_miss_s = n_miss_s + 1;
          if (n_miss_s < 5)
            $display("FAIL single-bit error UNDETECTED token=%0h bit=%0d",
                     i, bi);
        end
      end
    end
    if (n_miss_s != 0) errors = errors + 1;
    // Every one of them is also a reported downstream failure, because a
    // token is always host-to-device.
    ck("single-bit failures counted", n_tok_fail - base_tf, n_single);

    // ================= PHASE 3 -- every double-bit error, all 2048 =======
    //
    // 2048 x 120 = 245760 injections. This is the property that separates a
    // CRC from a parity bit.
    n_double = 0; n_miss_d = 0;
    for (i = 0; i < 2048; i = i + 1) begin
      // ...and again here, for the same reason.
      tok(i[10:0], ref_crc5(i[10:0]));
      cw = {5'd0, i[10:0]} | ({11'd0, ref_crc5(i[10:0])} << 11);
      for (bi = 0; bi < 16; bi = bi + 1)
        for (bj = bi + 1; bj < 16; bj = bj + 1) begin
          bad = cw ^ (16'd1 << bi) ^ (16'd1 << bj);
          tok(bad[10:0], bad[15:11]);
          n_double = n_double + 1;
          checks   = checks + 1;
          if (crc5_ok) begin
            n_miss_d = n_miss_d + 1;
            if (n_miss_d < 5)
              $display("FAIL double-bit error UNDETECTED token=%0h %0d,%0d",
                       i, bi, bj);
          end
        end
    end
    if (n_miss_d != 0) errors = errors + 1;

    // ================= PHASE 4 -- CRC16 over real packets ================
    //
    // Including the zero-length packet, which is a real packet with a real
    // CRC16 and the one length nobody tests.
    for (n = 0; n <= 16; n = n + 1) begin
      for (j = 0; j < n; j = j + 1) pay[j] = ($unsigned($random) % 256);
      c16 = pay_crc16(n);
      idle;
      datpkt(n, c16, 1'b0);
      ck("crc16_ok",    crc16_ok, 32'd1);
      ck("crc16_gen",   crc16_gen, c16);
      ck("crc16_resid", crc16_resid, CRC16_RESID);
    end

    // ================= PHASE 5 -- CRC16 single-bit detection =============
    //
    // Every bit of an 8-byte payload plus its 16-bit CRC: 80 positions,
    // over 64 different payloads. Not exhaustive over payloads -- 2^64 is
    // not a testbench -- but exhaustive over ERROR POSITION, which is the
    // axis the polynomial makes a promise about.
    base_df = n_dat_fail;
    n_single = 0;
    for (i = 0; i < 64; i = i + 1) begin
      for (j = 0; j < 8; j = j + 1) pay[j] = ($unsigned($random) % 256);
      c16 = pay_crc16(8);
      for (bi = 0; bi < 80; bi = bi + 1) begin
        // flip one bit: in the payload if bi < 64, in the CRC otherwise
        if (bi < 64) pay[bi/8] = pay[bi/8] ^ (8'd1 << (bi % 8));
        idle;
        datpkt(8, (bi >= 64) ? (c16 ^ (16'd1 << (bi - 64))) : c16, 1'b1);
        n_single = n_single + 1;
        checks   = checks + 1;
        if (crc16_ok) begin
          errors = errors + 1;
          if (errors < 25)
            $display("FAIL CRC16 single-bit error UNDETECTED payload=%0d bit=%0d",
                     i, bi);
        end
        if (bi < 64) pay[bi/8] = pay[bi/8] ^ (8'd1 << (bi % 8));
      end
    end
    ck("crc16 single-bit failures counted", n_dat_fail - base_df, n_single);
    // ...and every one of them was IN data, so every one is UPSTREAM. This
    // is the check that makes the verdict mean something: the direction is
    // not inferred from the failure, it is carried by the transaction.
    ck("all upstream", n_up_fail, n_single);

    // ================= PHASE 6 -- burst versus isolated ==================
    //
    // The measurement that separates marginal signal from interference. A
    // run of five failures is ONE isolated and FOUR burst; five failures
    // each separated by a good one are FIVE isolated.
    idle;
    tok(11'h123, ref_crc5(11'h123));     // a good one, to clear the run
    base_burst = n_burst; base_iso = n_isolated;
    for (i = 0; i < 5; i = i + 1) tok(11'h123, ref_crc5(11'h123) ^ 5'h01);
    if (n_burst - base_burst != 4 || n_isolated - base_iso != 1) begin
      errors = errors + 1;
      $display("FAIL run of 5: burst=%0d isolated=%0d expected 4 and 1",
               n_burst - base_burst, n_isolated - base_iso);
    end
    tok(11'h123, ref_crc5(11'h123));     // clear the run
    base_burst = n_burst; base_iso = n_isolated;
    for (i = 0; i < 5; i = i + 1) begin
      tok(11'h123, ref_crc5(11'h123) ^ 5'h01);
      tok(11'h123, ref_crc5(11'h123));
    end
    if (n_burst - base_burst != 0 || n_isolated - base_iso != 5) begin
      errors = errors + 1;
      $display("FAIL 5 separated: burst=%0d isolated=%0d expected 0 and 5",
               n_burst - base_burst, n_isolated - base_iso);
    end

    // ================= PHASE 7 -- the verdict ============================
    //
    // Three runs from a clean reset, each with the failures on one side of
    // the link, and the verdict has to name the right half.
    //
    // Reset between them, because a verdict is a RATIO over accumulated
    // counters: computed on top of the previous phase's totals it is a
    // ratio of the wrong things (chapter 25.4 phase 4, in a new costume).
    reset_dut;
    for (i = 0; i < 40; i = i + 1) tok(11'h055, ref_crc5(11'h055) ^ 5'h03);
    ck("verdict downstream", verdict, V_DOWN);

    reset_dut;
    for (j = 0; j < 8; j = j + 1) pay[j] = j[7:0];
    c16 = pay_crc16(8);
    for (i = 0; i < 40; i = i + 1) begin
      idle;
      datpkt(8, c16 ^ 16'h0001, 1'b1);       // IN data, wrong CRC
    end
    ck("verdict upstream", verdict, V_UP);

    reset_dut;
    for (i = 0; i < 20; i = i + 1) begin
      tok(11'h055, ref_crc5(11'h055) ^ 5'h03);
      idle;
      datpkt(8, c16 ^ 16'h0001, 1'b1);
    end
    ck("verdict common", verdict, V_COMMON);

    reset_dut;
    for (i = 0; i < 20; i = i + 1) tok(11'h055, ref_crc5(11'h055));
    ck("verdict clean", verdict, V_CLEAN);

    $display("steps=%0d checks=%0d errors=%0d", steps, checks, errors);
    $display("single-bit injections=%0d undetected=%0d", 2048*16, n_miss_s);
    $display("double-bit injections=%0d undetected=%0d", 2048*120, n_miss_d);
    $display("crc5 residual values observed=%0d (expected 1)", n_resid);
    $display("%0s: %0d errors in %0d checks",
             (errors == 0) ? "PASS" : "FAIL", errors, checks);
    $finish;
  end

  task automatic reset_dut;
    begin
      tok_valid = 1'b0; dat_start = 1'b0; dat_valid = 1'b0; dat_end = 1'b0;
      rst_n = 1'b0;
      @(posedge clk); #1;
      rst_n = 1'b1;
      @(negedge clk);
      // The DUT's counters went to zero, so the expectations must too --
      // otherwise every check after the first reset fails for a reason that
      // has nothing to do with the design.
      clear_expect;
    end
  endtask
endmodule

VHDL-2008 testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Testbench for usb_crc_analyzer (VHDL-2008).
--
-- THE ORACLE IS A BIT-SERIAL REFERENCE, WRITTEN INDEPENDENTLY
--
-- A CRC is the one block where "the model agrees with the design" is worth
-- almost nothing, because both were probably derived from the same
-- half-remembered table. So the reference here is anchored to something
-- outside both: the USB 2.0 specification's own worked example --
--
--     address 0x15, endpoint 0xE   ->   CRC5 = 0x17
--
-- -- which is checked first, before anything else runs. If that one value is
-- wrong, every other number in this file is a consistent lie.
--
-- THE EXHAUSTIVE CLAIMS
--
--   1. all 2048 token values: the generated CRC5 matches the reference
--   2. all 2048 token values: the residual of the correct codeword is the
--      SAME CONSTANT (this is what makes residual checking legal at all)
--   3. all 2048 x 16 = 32768 single-bit errors are DETECTED
--   4. all 2048 x 120 = 245760 double-bit errors are DETECTED
--
-- The last two are the properties the polynomial was chosen for, and they
-- are the only ones that matter: a CRC that computes a repeatable value and
-- detects nothing is a checksum with extra steps.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.usb_crc_pkg.all;

entity tb_ca_vhdl is
end entity;

architecture sim of tb_ca_vhdl is
  constant BURST_MIN : integer := 2;

  signal clk       : std_logic := '0';
  signal rst_n     : std_logic := '0';
  signal tok_valid : std_logic := '0';
  signal tok_data  : std_logic_vector(10 downto 0) := (others => '0');
  signal tok_crc5  : std_logic_vector(4 downto 0)  := (others => '0');
  signal dat_start : std_logic := '0';
  signal dat_valid : std_logic := '0';
  signal dat_byte  : std_logic_vector(7 downto 0)  := (others => '0');
  signal dat_end   : std_logic := '0';
  signal dat_crc16 : std_logic_vector(15 downto 0) := (others => '0');
  signal dat_dir   : std_logic := '0';
  signal eot       : std_logic := '0';

  signal crc5_gen_s    : std_logic_vector(4 downto 0);
  signal crc5_resid_s  : std_logic_vector(4 downto 0);
  signal crc5_ok_s     : std_logic;
  signal crc16_gen_s   : std_logic_vector(15 downto 0);
  signal crc16_resid_s : std_logic_vector(15 downto 0);
  signal crc16_ok_s    : std_logic;
  signal fail_pulse_s  : std_logic;
  signal fail_field_s  : std_logic;
  signal fail_run_s    : std_logic_vector(7 downto 0);
  signal verdict_s     : std_logic_vector(2 downto 0);
  signal bursty_s      : std_logic;

  signal n_tok_s, n_tok_fail_s, n_dat_s, n_dat_fail_s : unsigned(31 downto 0);
  signal n_down_s, n_up_s, n_burst_s, n_iso_s         : unsigned(31 downto 0);

  signal done : boolean := false;

  type byte_array is array (natural range <>) of std_logic_vector(7 downto 0);
  type int_array  is array (natural range <>) of integer;
begin
  clk <= not clk after 5 ns when not done else '0';

  dut : entity work.usb_crc_analyzer
    generic map (BURST_MIN => BURST_MIN)
    port map (
      clk => clk, rst_n => rst_n,
      tok_valid => tok_valid, tok_data => tok_data, tok_crc5 => tok_crc5,
      dat_start => dat_start, dat_valid => dat_valid, dat_byte => dat_byte,
      dat_end => dat_end, dat_crc16 => dat_crc16, dat_dir => dat_dir,
      eot => eot,
      crc5_gen => crc5_gen_s, crc5_resid => crc5_resid_s,
      crc5_ok => crc5_ok_s,
      crc16_gen => crc16_gen_s, crc16_resid => crc16_resid_s,
      crc16_ok => crc16_ok_s,
      fail_pulse => fail_pulse_s, fail_field => fail_field_s,
      fail_run => fail_run_s, verdict => verdict_s, bursty => bursty_s,
      n_tok => n_tok_s, n_tok_fail => n_tok_fail_s,
      n_dat => n_dat_s, n_dat_fail => n_dat_fail_s,
      n_down_fail => n_down_s, n_up_fail => n_up_s,
      n_burst => n_burst_s, n_isolated => n_iso_s
    );

  stim : process
    variable errors, checks, steps : integer := 0;
    variable n_single, n_double    : integer := 0;
    variable n_miss_s, n_miss_d    : integer := 0;
    variable n_resid               : integer := 0;
    variable seen : int_array(0 to 31) := (others => 0);
    variable pay  : byte_array(0 to 63);
    variable cw, bad : unsigned(15 downto 0);
    variable g   : unsigned(4 downto 0);
    variable c16 : unsigned(15 downto 0);
    variable base_burst, base_iso, base_tf, base_df : integer := 0;
    variable ln : line;

    -- ---- a shadow model of the CLASSIFICATION ----
    --
    -- The phases below prove the CRC arithmetic exhaustively, which is the
    -- hard part and not the whole part: the block also has to decide which
    -- HALF of the link a failure belongs to and whether it arrived in a run.
    -- Checked only by the directed assertions in phases 5 to 7, the
    -- mutations that broke those two decisions scored 3 and 2 against scores
    -- in the thousands for everything else. A mutation that survives on a
    -- handful of checks has not been killed, it has been cornered.
    variable e_down, e_up, e_burst, e_iso, e_run : integer := 0;
    variable e_tokfail, e_datfail                : integer := 0;

    -- A deterministic LFSR, so a rerun reproduces exactly the same payloads.
    variable lfsr : unsigned(31 downto 0) := x"1BADC0DE";

    impure function rnd_byte return std_logic_vector is
    begin
      lfsr := lfsr(30 downto 0) &
              (lfsr(31) xor lfsr(21) xor lfsr(1) xor lfsr(0));
      return std_logic_vector(lfsr(7 downto 0));
    end function;

    procedure ck (nm : string; got, exp : integer) is
    begin
      checks := checks + 1;
      if got /= exp then
        errors := errors + 1;
        if errors < 25 then
          write(ln, string'("FAIL step=") & integer'image(steps) & " " & nm
                    & " got=" & integer'image(got)
                    & " exp=" & integer'image(exp));
          writeline(output, ln);
        end if;
      end if;
    end procedure;

    -- ---------------- the independent bit-serial reference ---------------
    --
    -- Written as a loop over single bits rather than as a table or an
    -- unrolled XOR tree, because the loop IS the definition of the
    -- polynomial division and the other two forms are optimisations of it
    -- that have to be checked against something.
    function ref_crc5 (d : unsigned(10 downto 0)) return unsigned is
      variable c : unsigned(4 downto 0) := "11111";
      variable x : std_logic;
    begin
      for i in 0 to 10 loop
        x := d(i) xor c(4);
        c := c(3 downto 0) & '0';
        if x = '1' then c := c xor "00101"; end if;
      end loop;
      return not c;
    end function;

    function ref_crc16_byte (c0 : unsigned(15 downto 0);
                             b  : std_logic_vector(7 downto 0))
      return unsigned is
      variable c : unsigned(15 downto 0) := c0;
      variable x : std_logic;
    begin
      for i in 0 to 7 loop
        x := b(i) xor c(15);
        c := c(14 downto 0) & '0';
        if x = '1' then c := c xor x"8005"; end if;
      end loop;
      return c;
    end function;

    -- Called on the cycle the design consumes an event. `ok` is computed
    -- from the reference, never read back from the design.
    procedure classify (ok : boolean; is_data : boolean; dir : std_logic) is
    begin
      if not ok then
        if is_data then
          e_datfail := e_datfail + 1;
          if dir = '1' then e_up := e_up + 1; else e_down := e_down + 1; end if;
        else
          e_tokfail := e_tokfail + 1;
          -- A token is ALWAYS host-to-device. The model asserts that as a
          -- fact about the protocol rather than reading a direction input.
          e_down := e_down + 1;
        end if;
        if e_run >= 1 then e_burst := e_burst + 1;
        else               e_iso   := e_iso   + 1;
        end if;
        if e_run /= 255 then e_run := e_run + 1; end if;
      else
        e_run := 0;
      end if;
      ck("n_tok_fail",  to_integer(n_tok_fail_s), e_tokfail);
      ck("n_dat_fail",  to_integer(n_dat_fail_s), e_datfail);
      ck("n_down_fail", to_integer(n_down_s),     e_down);
      ck("n_up_fail",   to_integer(n_up_s),       e_up);
      ck("n_burst",     to_integer(n_burst_s),    e_burst);
      ck("n_isolated",  to_integer(n_iso_s),      e_iso);
      ck("fail_run",    to_integer(unsigned(fail_run_s)), e_run);
      -- Every failure is exactly one of burst or isolated, and exactly one
      -- of upstream or downstream. A report that cannot be decomposed
      -- cannot be trusted (chapter 23.4).
      ck("burst+iso", to_integer(n_burst_s) + to_integer(n_iso_s),
                      to_integer(n_tok_fail_s) + to_integer(n_dat_fail_s));
      ck("up+down",   to_integer(n_down_s) + to_integer(n_up_s),
                      to_integer(n_tok_fail_s) + to_integer(n_dat_fail_s));
    end procedure;

    procedure clear_expect is
    begin
      e_down := 0; e_up := 0; e_burst := 0; e_iso := 0; e_run := 0;
      e_tokfail := 0; e_datfail := 0;
    end procedure;

    -- The reference residual, for predicting whether a token will pass.
    function ref_crc5_resid (d : unsigned(10 downto 0); r : unsigned(4 downto 0))
      return unsigned is
      variable c : unsigned(4 downto 0) := "11111";
      variable x : std_logic;
    begin
      for i in 0 to 10 loop
        x := d(i) xor c(4);
        c := c(3 downto 0) & '0';
        if x = '1' then c := c xor "00101"; end if;
      end loop;
      for i in 4 downto 0 loop
        x := r(i) xor c(4);
        c := c(3 downto 0) & '0';
        if x = '1' then c := c xor "00101"; end if;
      end loop;
      return c;
    end function;

    procedure tok (d : unsigned(10 downto 0); c : unsigned(4 downto 0)) is
    begin
      tok_valid <= '1';
      tok_data  <= std_logic_vector(d);
      tok_crc5  <= std_logic_vector(c);
      dat_start <= '0'; dat_valid <= '0'; dat_end <= '0'; eot <= '0';
      wait for 0 ns;
      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;
      classify(ref_crc5_resid(d, c) = CRC5_RESIDUAL, false, '0');
      tok_valid <= '0';
    end procedure;

    procedure idle is
    begin
      tok_valid <= '0'; dat_start <= '0'; dat_valid <= '0';
      dat_end <= '0'; eot <= '0';
      wait for 0 ns;
      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;
    end procedure;

    impure function pay_crc16 (n : integer) return unsigned is
      variable c : unsigned(15 downto 0) := x"FFFF";
    begin
      for j in 0 to n-1 loop
        c := ref_crc16_byte(c, pay(j));
      end loop;
      return not c;
    end function;

    -- A whole data packet: start, n bytes, end. The CRC16 is passed in
    -- rather than computed here, so a test can hand over a WRONG one.
    procedure datpkt (n : integer; c : unsigned(15 downto 0); dir : std_logic) is
    begin
      dat_dir <= dir;
      tok_valid <= '0'; eot <= '0';
      if n = 0 then
        -- A zero-length packet is a real packet with a real CRC16. It is
        -- also the one length nobody tests.
        dat_start <= '1'; dat_valid <= '0'; dat_end <= '0';
        wait for 0 ns;
        wait until rising_edge(clk);
        wait for 1 ns;
        steps := steps + 1;
      else
        for j in 0 to n-1 loop
          if j = 0 then dat_start <= '1'; else dat_start <= '0'; end if;
          dat_valid <= '1';
          dat_byte  <= pay(j);
          dat_end   <= '0';
          wait for 0 ns;
          wait until rising_edge(clk);
          wait for 1 ns;
          steps := steps + 1;
        end loop;
      end if;
      dat_start <= '0'; dat_valid <= '0';
      dat_end   <= '1'; dat_crc16 <= std_logic_vector(c);
      wait for 0 ns;
      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;
      classify(c = pay_crc16(n), true, dir);
      dat_end <= '0';
    end procedure;

    procedure reset_dut is
    begin
      tok_valid <= '0'; dat_start <= '0'; dat_valid <= '0'; dat_end <= '0';
      rst_n <= '0';
      wait until rising_edge(clk);
      wait for 1 ns;
      rst_n <= '1';
      wait for 1 ns;
      -- The DUT's counters went to zero, so the expectations must too --
      -- otherwise every check after the first reset fails for a reason that
      -- has nothing to do with the design.
      clear_expect;
    end procedure;
  begin
    wait until rising_edge(clk);
    wait until rising_edge(clk);
    wait until rising_edge(clk);
    rst_n <= '1';
    wait for 1 ns;

    -- ================= PHASE 0 -- the specification's own example ========
    --
    -- Before anything else. Address 0x15, endpoint 0xE, CRC5 = 0x17, from
    -- the USB 2.0 specification. Every other number in this file depends on
    -- this one being right, and nothing inside this testbench could tell you
    -- if it were not.
    tok("11100010101", ref_crc5("11100010101"));
    ck("spec example CRC5", to_integer(unsigned(crc5_gen_s)), 16#17#);
    ck("spec example ref",  to_integer(ref_crc5("11100010101")), 16#17#);
    ck("spec example ok",   to_integer(unsigned'("" & crc5_ok_s)), 1);

    -- ================= PHASE 1 -- all 2048 tokens, generated and checked ==
    for i in 0 to 2047 loop
      g := ref_crc5(to_unsigned(i, 11));
      tok(to_unsigned(i, 11), g);
      ck("crc5_gen",   to_integer(unsigned(crc5_gen_s)),   to_integer(g));
      ck("crc5_ok",    to_integer(unsigned'("" & crc5_ok_s)), 1);
      ck("crc5_resid", to_integer(unsigned(crc5_resid_s)),
                       to_integer(CRC5_RESIDUAL));
      -- ...and record the residual actually observed, so the claim "it is a
      -- constant" is MEASURED rather than assumed.
      if seen(to_integer(unsigned(crc5_resid_s))) = 0 then
        seen(to_integer(unsigned(crc5_resid_s))) := 1;
        n_resid := n_resid + 1;
      end if;
    end loop;
    if n_resid /= 1 then
      errors := errors + 1;
      write(ln, string'("FAIL the CRC5 residual is not a constant: ")
                & integer'image(n_resid) & " values seen");
      writeline(output, ln);
    end if;

    -- ================= PHASE 2 -- every single-bit error, all 2048 =======
    --
    -- 2048 x 16 = 32768 injections. The polynomial detects all of them or it
    -- is the wrong polynomial.
    base_tf := to_integer(n_tok_fail_s);
    for i in 0 to 2047 loop
      -- ---- A CORRECT codeword first, every time. ----
      --
      -- Without this the phase drives tens of thousands of CONSECUTIVE
      -- failures and never once follows a failure with a success -- which is
      -- the only transition the run-reset logic has. The mutation that
      -- deletes that reset scored 51 against scores in the millions, not
      -- because the check was weak but because the stimulus never produced
      -- the situation it checks.
      tok(to_unsigned(i, 11), ref_crc5(to_unsigned(i, 11)));
      cw := resize(to_unsigned(i, 11), 16) or
            shift_left(resize(ref_crc5(to_unsigned(i, 11)), 16), 11);
      for bi in 0 to 15 loop
        bad := cw xor shift_left(to_unsigned(1, 16), bi);
        tok(bad(10 downto 0), bad(15 downto 11));
        n_single := n_single + 1;
        checks   := checks + 1;
        if crc5_ok_s = '1' then
          n_miss_s := n_miss_s + 1;
          if n_miss_s < 5 then
            write(ln, string'("FAIL single-bit error UNDETECTED token=")
                      & integer'image(i) & " bit=" & integer'image(bi));
            writeline(output, ln);
          end if;
        end if;
      end loop;
    end loop;
    if n_miss_s /= 0 then errors := errors + 1; end if;
    -- Every one of them is also a reported downstream failure, because a
    -- token is always host-to-device.
    ck("single-bit failures counted",
       to_integer(n_tok_fail_s) - base_tf, n_single);

    -- ================= PHASE 3 -- every double-bit error, all 2048 =======
    --
    -- 2048 x 120 = 245760 injections. This is the property that separates a
    -- CRC from a parity bit.
    for i in 0 to 2047 loop
      -- ...and again here, for the same reason.
      tok(to_unsigned(i, 11), ref_crc5(to_unsigned(i, 11)));
      cw := resize(to_unsigned(i, 11), 16) or
            shift_left(resize(ref_crc5(to_unsigned(i, 11)), 16), 11);
      for bi in 0 to 15 loop
        for bj in bi+1 to 15 loop
          bad := cw xor shift_left(to_unsigned(1, 16), bi)
                     xor shift_left(to_unsigned(1, 16), bj);
          tok(bad(10 downto 0), bad(15 downto 11));
          n_double := n_double + 1;
          checks   := checks + 1;
          if crc5_ok_s = '1' then
            n_miss_d := n_miss_d + 1;
            if n_miss_d < 5 then
              write(ln, string'("FAIL double-bit error UNDETECTED token=")
                        & integer'image(i) & " " & integer'image(bi)
                        & "," & integer'image(bj));
              writeline(output, ln);
            end if;
          end if;
        end loop;
      end loop;
    end loop;
    if n_miss_d /= 0 then errors := errors + 1; end if;

    -- ================= PHASE 4 -- CRC16 over real packets ================
    --
    -- Including the zero-length packet, which is a real packet with a real
    -- CRC16 and the one length nobody tests.
    for n in 0 to 16 loop
      for j in 0 to n-1 loop pay(j) := rnd_byte; end loop;
      c16 := pay_crc16(n);
      idle;
      datpkt(n, c16, '0');
      ck("crc16_ok",    to_integer(unsigned'("" & crc16_ok_s)), 1);
      ck("crc16_gen",   to_integer(unsigned(crc16_gen_s)), to_integer(c16));
      ck("crc16_resid", to_integer(unsigned(crc16_resid_s)),
                        to_integer(CRC16_RESIDUAL));
    end loop;

    -- ================= PHASE 5 -- CRC16 single-bit detection =============
    --
    -- Every bit of an 8-byte payload plus its 16-bit CRC: 80 positions, over
    -- 64 different payloads. Not exhaustive over payloads -- 2^64 is not a
    -- testbench -- but exhaustive over ERROR POSITION, which is the axis the
    -- polynomial makes a promise about.
    base_df  := to_integer(n_dat_fail_s);
    n_single := 0;
    for i in 0 to 63 loop
      for j in 0 to 7 loop pay(j) := rnd_byte; end loop;
      c16 := pay_crc16(8);
      for bi in 0 to 79 loop
        if bi < 64 then
          pay(bi/8) := pay(bi/8) xor
                       std_logic_vector(shift_left(to_unsigned(1, 8), bi mod 8));
        end if;
        idle;
        if bi >= 64 then
          datpkt(8, c16 xor shift_left(to_unsigned(1, 16), bi - 64), '1');
        else
          datpkt(8, c16, '1');
        end if;
        n_single := n_single + 1;
        checks   := checks + 1;
        if crc16_ok_s = '1' then
          errors := errors + 1;
          if errors < 25 then
            write(ln, string'("FAIL CRC16 single-bit UNDETECTED payload=")
                      & integer'image(i) & " bit=" & integer'image(bi));
            writeline(output, ln);
          end if;
        end if;
        if bi < 64 then
          pay(bi/8) := pay(bi/8) xor
                       std_logic_vector(shift_left(to_unsigned(1, 8), bi mod 8));
        end if;
      end loop;
    end loop;
    ck("crc16 single-bit failures counted",
       to_integer(n_dat_fail_s) - base_df, n_single);
    -- ...and every one of them was IN data, so every one is UPSTREAM. This
    -- is the check that makes the verdict mean something: the direction is
    -- not inferred from the failure, it is carried by the transaction.
    ck("all upstream", to_integer(n_up_s), n_single);

    -- ================= PHASE 6 -- burst versus isolated ==================
    --
    -- The measurement that separates marginal signal from interference. A
    -- run of five failures is ONE isolated and FOUR burst; five failures
    -- each separated by a good one are FIVE isolated.
    idle;
    tok("00100100011", ref_crc5("00100100011"));
    base_burst := to_integer(n_burst_s); base_iso := to_integer(n_iso_s);
    for i in 0 to 4 loop
      tok("00100100011", ref_crc5("00100100011") xor "00001");
    end loop;
    if (to_integer(n_burst_s) - base_burst) /= 4
       or (to_integer(n_iso_s) - base_iso) /= 1 then
      errors := errors + 1;
      write(ln, string'("FAIL run of 5: burst=")
                & integer'image(to_integer(n_burst_s) - base_burst)
                & " isolated=" & integer'image(to_integer(n_iso_s) - base_iso)
                & " expected 4 and 1");
      writeline(output, ln);
    end if;
    tok("00100100011", ref_crc5("00100100011"));
    base_burst := to_integer(n_burst_s); base_iso := to_integer(n_iso_s);
    for i in 0 to 4 loop
      tok("00100100011", ref_crc5("00100100011") xor "00001");
      tok("00100100011", ref_crc5("00100100011"));
    end loop;
    if (to_integer(n_burst_s) - base_burst) /= 0
       or (to_integer(n_iso_s) - base_iso) /= 5 then
      errors := errors + 1;
      write(ln, string'("FAIL 5 separated: burst=")
                & integer'image(to_integer(n_burst_s) - base_burst)
                & " isolated=" & integer'image(to_integer(n_iso_s) - base_iso)
                & " expected 0 and 5");
      writeline(output, ln);
    end if;

    -- ================= PHASE 7 -- the verdict ============================
    --
    -- Three runs from a clean reset, each with the failures on one side of
    -- the link, and the verdict has to name the right half.
    --
    -- Reset between them, because a verdict is a RATIO over accumulated
    -- counters: computed on top of the previous phase's totals it is a ratio
    -- of the wrong things (chapter 25.4 phase 4, in a new costume).
    reset_dut;
    for i in 0 to 39 loop
      tok("00001010101", ref_crc5("00001010101") xor "00011");
    end loop;
    ck("verdict downstream", to_integer(unsigned(verdict_s)),
       to_integer(unsigned(V_DOWN)));

    reset_dut;
    for j in 0 to 7 loop pay(j) := std_logic_vector(to_unsigned(j, 8)); end loop;
    c16 := pay_crc16(8);
    for i in 0 to 39 loop
      idle;
      datpkt(8, c16 xor x"0001", '1');       -- IN data, wrong CRC
    end loop;
    ck("verdict upstream", to_integer(unsigned(verdict_s)),
       to_integer(unsigned(V_UP)));

    reset_dut;
    for i in 0 to 19 loop
      tok("00001010101", ref_crc5("00001010101") xor "00011");
      idle;
      datpkt(8, c16 xor x"0001", '1');
    end loop;
    ck("verdict common", to_integer(unsigned(verdict_s)),
       to_integer(unsigned(V_COMMON)));

    reset_dut;
    for i in 0 to 19 loop
      tok("00001010101", ref_crc5("00001010101"));
    end loop;
    ck("verdict clean", to_integer(unsigned(verdict_s)),
       to_integer(unsigned(V_CLEAN)));

    write(ln, string'("steps=") & integer'image(steps)
              & " checks=" & integer'image(checks)
              & " errors=" & integer'image(errors));
    writeline(output, ln);
    write(ln, string'("single-bit injections=32768 undetected=")
              & integer'image(n_miss_s));
    writeline(output, ln);
    write(ln, string'("double-bit injections=245760 undetected=")
              & integer'image(n_miss_d));
    writeline(output, ln);
    write(ln, string'("crc5 residual values observed=")
              & integer'image(n_resid) & " (expected 1)");
    writeline(output, ln);
    if errors = 0 then
      write(ln, string'("PASS: 0 errors in ") & integer'image(checks)
                & " checks");
    else
      write(ln, string'("FAIL: ") & integer'image(errors) & " errors in " &
                integer'image(checks) & " checks");
    end if;
    writeline(output, ln);
    done <= true;
    wait;
  end process;
end architecture;

11. Exhaustive Verification

MeasureVerilogSystemVerilogVHDL
token values swept2048 / 20482048 / 20482048 / 2048
single-bit errors detected32768 / 3276832768 / 3276832768 / 32768
double-bit errors detected245760 / 245760245760 / 245760245760 / 245760
distinct CRC5 residuals observed111
CRC5 of the spec's example0x170x170x17
Steps332646332646332646
Checks executed289955628995562899556
ResultPASSPASSPASS

All three columns are identical, and here that is not the coincidence it was in 25.4. This testbench has no random phase at all: every stimulus in it is enumerated, so there is nothing for a random seed to differ on. The only thing that varies between the languages is the payload bytes in the CRC16 phases, and the detection properties being measured do not depend on them.

12. Mutation Testing

#MutationVerilogSysVerVHDL
S2CRC5 starts at zero instead of all ones145008614500861450086
S3the token is fed MSB first144094414409441440944
S6a good codeword does not reset the failure run858146858146858146
S7one bit of the CRC16 polynomial (0x8004)209202092020920
S4the CRC16 register is reset instead of absorbing byte 0157921579215792
S5the direction is ignored; all failures counted downstream104351043510435
S1the CRC5 result is not complemented204920492049
—unmutated baseline000

All seven die in all three languages.

S1 scores exactly 2049, and the number is the whole story. The mutation drops the final complement from the generator only, so crc5_gen is wrong while the residual check still passes — a design that reports the wrong expected value and accepts the right one. 2049 is 2048 crc5_gen checks plus the one in the specification-example phase, and nothing else. A block that is wrong about what the CRC should be, and right about whether it is, is caught by exactly the checks that ask the first question.

S3 is the one the spec example exists for. Feeding the token MSB-first changes all 2048 values and the design stays perfectly self-consistent: it generates a CRC, it checks that CRC, and generator and checker agree. Every internal consistency check passes. Only the external anchor — address 0x15, endpoint 0xE, CRC5 0x17 — can tell, which is why it is checked before anything else runs.

13. Two Mutations That Nearly Escaped, and What Fixed Them

S5 scored 3. S6 scored 2. Against scores in the millions.

Neither was a weak mutation. The testbench proved the CRC arithmetic exhaustively and then checked the classification — which half of the link, burst or isolated — with six directed assertions at the end. So a mutation that broke only the classification had six chances to die, and took three of them.

S6 went from 2 to 51, which was still the lowest score in the table, and the second reason was stimulus rather than checking.

S6 deletes the line that resets the failure-run counter on a good codeword. To observe that, you need a good codeword immediately after a bad one. The exhaustive phases drove 278,528 corrupted tokens back to back and never once followed a failure with a success:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   what phase 2 drove      X X X X X X X X X X X X X X X ...
   what S6 needs           X X . X X . X X . X X . X X . ...

   278,528 injections, and not one instance of the
   transition the deleted line is responsible for.

One correct codeword at the top of each token's inner loop — 4,096 of them across the two phases — and S6 went from 51 to 858,146.

14. Debugging Walkthrough: The Hub That Fixed It

The report. A test rig drops a full-speed industrial sensor every few minutes. The CRC error counter on the host reads in the tens of thousands. Plugging the sensor into a cheap high-speed hub instead of directly into the machine makes the problem disappear, which everyone agrees makes no sense.

Step 1 — split the counter by field. Total CRC errors is one number covering two independent failure mechanisms. Split it: CRC5 = 0. CRC16 = 41,900. Every single failure is on data, and none on tokens.

Step 2 — split by direction. All 41,900 are on IN transfers. So the downstream half — host to device — is clean, and the damage is on the device's transmitter or the upstream signal path.

Step 3 — split by clustering. 39,200 burst, 2,700 isolated. The failures arrive in runs, so this is not marginal signal: something periodic is doing it.

Step 4 — what is periodic, and why only upstream? The rig drives a stepper motor whose cable runs parallel to the sensor's 3 m USB lead for most of its length. The device's upstream drive is the weaker of the two directions into that lead, so it is the one that loses.

Step 5 — so why does the hub help? Because the sensor is a full-speed device behind a high-speed hub, and that is not a repeater arrangement. A high-speed hub contains a transaction translator: it terminates the full-speed transaction itself, checks its CRC16, and retries it locally, then hands the host a completed high-speed transaction. The corrupted packets never reach the host's counter.

Step 6 — and the rig still drops frames. Less often, and now with no CRC errors visible anywhere, because the layer that is absorbing them does not report to the host. The cable is still picking up the motor; the hub has moved the evidence.

Three splits — field, direction, clustering — took the diagnosis from "tens of thousands of CRC errors" to "the motor cable couples into the sensor's upstream lead", and none of the three needs anything the analyser did not already capture. The information was in the count all along; the count just could not express it.

15. UVM: A CRC Agent That Refuses to Reuse the RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The single most common way to get a CRC verification environment wrong is
// to build the reference out of the same function the design uses. It is
// right there, it is already written, and it makes the whole testbench
// agree with itself perfectly while both are wrong.
//
// So this package has its own, deliberately written in the slowest and most
// obvious form -- a loop over single bits -- and anchored to a value from
// outside the project.
package usb_crc_ref_pkg;

  // THE ANCHOR. From the USB 2.0 specification, not from this project.
  // Checked in the environment's start_of_simulation_phase, before any
  // stimulus runs, so that a broken reference stops the run rather than
  // producing a confidently wrong report.
  localparam logic [10:0] SPEC_TOKEN = 11'h715;   // address 0x15, endpoint 0xE
  localparam logic [4:0]  SPEC_CRC5  = 5'h17;

  function automatic logic [4:0] ref_crc5(logic [10:0] d);
    logic [4:0] c = 5'h1F;
    logic       x;
    for (int i = 0; i < 11; i++) begin       // LSB first: wire order
      x = d[i] ^ c[4];
      c = {c[3:0], 1'b0};
      if (x) c ^= 5'h05;
    end
    return ~c;
  endfunction
endpackage


// ---------------------------------------------------------------------
// The subscriber. Note what it does NOT do: it does not count CRC errors.
// A single CRC error count is the one number this chapter argues is
// useless, and a verification component that produces it has added a
// metric the team will then rely on.
// ---------------------------------------------------------------------
class usb_crc_sub extends uvm_subscriber #(usb_crc_item);
  `uvm_component_utils(usb_crc_sub)
  import usb_crc_ref_pkg::*;

  // Split three ways, always. Field, direction, clustering.
  int unsigned n_tok_fail, n_dat_fail;
  int unsigned n_down, n_up;
  int unsigned n_burst, n_isolated;
  int unsigned run;

  function new(string name, uvm_component parent); super.new(name, parent);
  endfunction

  // The anchor check. Before any stimulus.
  function void start_of_simulation_phase(uvm_phase phase);
    super.start_of_simulation_phase(phase);
    if (ref_crc5(SPEC_TOKEN) !== SPEC_CRC5)
      `uvm_fatal("CRC/REF",
        $sformatf("the reference is wrong: spec says CRC5(0x%03h) = 0x%02h, got 0x%02h",
                  SPEC_TOKEN, SPEC_CRC5, ref_crc5(SPEC_TOKEN)))
  endfunction

  function void write(usb_crc_item t);
    bit ok;
    ok = t.is_token ? (t.crc5 === ref_crc5(t.token))
                    : (t.crc16 === ref_crc16(t.payload));
    if (ok) begin
      run = 0;
      return;
    end

    // ---- Field. ----
    if (t.is_token) begin
      n_tok_fail++;
      // A token is ALWAYS host-to-device. Asserted as a fact about the
      // protocol rather than read from a direction field that a broken
      // monitor could have filled in wrongly.
      n_down++;
    end else begin
      n_dat_fail++;
      if (t.dir_in) n_up++; else n_down++;
    end

    // ---- Clustering. ----
    if (run > 0) n_burst++; else n_isolated++;
    run++;
  endfunction

  // ---- The report is three RATIOS, and no total. ----
  //
  // Deliberately. "41,900 CRC errors" is the number that sent section 14's
  // rig round in circles twice; the three splits are what ended it.
  function void report_phase(uvm_phase phase);
    int unsigned total = n_tok_fail + n_dat_fail;
    string half, mech;
    super.report_phase(phase);
    if (total == 0) begin
      `uvm_info("CRC", "no CRC failures", UVM_LOW)
      return;
    end

    half = (n_down > 2 * n_up)   ? "DOWNSTREAM (host -> device)" :
           (n_up   > 2 * n_down) ? "UPSTREAM (device -> host)"   :
                                   "BOTH: common mode -- power, ground, connector";
    mech = (n_burst > n_isolated)
           ? "BURSTY: something periodic (a switching supply, a fan, a motor)"
           : "SCATTERED: marginal signal, a closing eye, a thin margin";

    `uvm_info("CRC",
      $sformatf("token %0d / data %0d | %s | %s (%0d burst, %0d isolated)",
                n_tok_fail, n_dat_fail, half, mech, n_burst, n_isolated),
      UVM_LOW)

    // A token CRC failure is unambiguous evidence about one half of the
    // link, so it is worth naming separately even when the totals are low.
    if (n_tok_fail > 0 && n_dat_fail == 0)
      `uvm_warning("CRC/TOKEN_ONLY",
        "every failure is on a token: the DOWNSTREAM path is damaged and the upstream path is clean")
  endfunction
endclass

16. Common Misconceptions

"A CRC tells you data was corrupted." It tells you this codeword is not a valid one. Which bits, and how many, it cannot say.

"CRC error count is a link-quality metric." It is three metrics added together, and the sum discards all three.

"CRC errors mean a bad cable." They mean the signal was corrupted. A cable is one cause among several, and CRC5-versus-CRC16 already narrows it to a half.

"Initial value and final complement are cosmetic." Without them an all-zero codeword — a dead transmitter — is valid, and leading zeros do not affect the result.

"Check the CRC by recomputing and comparing." A receiver checks the residual: the same register, fed the CRC too, leaves a constant.

"The residual constant is in the spec, so it can be quoted." A wrong constant produces a checker that passes everything and reports nothing. Measure it.

"Testing a few known vectors verifies a CRC." It verifies that the implementation reproduces a number. The detection properties are the point and they need the exhaustive sweep.

"Exhaustive over every bit position is exhaustive." Phase 2 enumerated every corruption and never produced a recovery, which is why S6 nearly escaped.

"Three-bit errors should be tested too." They should be understood: the guarantee there is probabilistic, so a test would measure a statistic and report it as a property.

17. Exercises

1. Show that without the final complement an all-zero 16-bit codeword passes the CRC5 residual check, and say what physical failure produces exactly that.

2. S1 scores exactly 2049. Identify both contributing checks and explain why the residual check contributes nothing.

3. The CRC5 residual is 0x0C for all 2048 tokens. Prove it must be a constant for any input, given that the codeword is by construction divisible by the generator polynomial.

4. S3 feeds the token MSB-first and every internal check still passes. Construct one more external anchor that would also catch it, and say why two anchors are better than one.

5. CRC16 with x^16+x^15+x^2+1 detects all bursts up to 16 bits. Work out what that means for a 64-byte payload and whether the guarantee covers a whole corrupted byte.

6. The burst/isolated split counts a run of five as one isolated and four burst. Propose a measure that distinguishes a run of five from five runs of one more directly, and say what it costs.

7. The UVM subscriber refuses to emit a total. Argue the other side, then say what you would have to add to the report to make a total safe.

18. Summary

IdeaWhy it matters
Two polynomials, two scopesCRC5 covers the token, CRC16 covers the data
A token is always host-to-deviceso a CRC5 failure names a half of the link with no ambiguity
The field names the directionCRC16 on IN is upstream; on OUT it is downstream
Both halves failing is common modepower, ground, the connector, the whole cable
The clustering names the mechanismscattered is marginal signal; runs are interference
Init all-ones and final complementor an all-zero codeword — a dead transmitter — is valid
A receiver checks the residualone register, and a comparison against a constant
Measure the residual constanta wrong one passes everything and reports nothing
Detection is the property, not the valuea repeatable number with no detection is a checksum
Anchor to something outside the projecta self-consistent CRC can be wrong in all 2048 values
A low score can be stimulus, not checkingS6: 278,528 corruptions, not one recovery
A cornered mutation is not a killed oneS5 lived on three checks until the classification was modelled
2048 tokens, 32768 + 245760 injections7 mutations, all killed in 3 languages

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o ca_v.out ca_v.v ca_v_tb.v && ./ca_v.out
SystemVerilogiverilog -g2012 -o ca_sv.out ca_sv.sv ca_sv_tb.sv && ./ca_sv.out
VHDL-2008 analysenvc --std=2008 -a ca_vhdl.vhd ca_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_ca_vhdl
VHDL-2008 runnvc --std=2008 -r tb_ca_vhdl
One mutationiverilog -g2005 -DMUT_S3 -o mm ca_v_mut.v ca_v_tb.v && ./mm

All three implementations pass with 0 errors: the specification's own worked example reproduced, all 2048 token values generated and residual-checked, a single distinct residual observed across all of them, all 32768 single-bit and all 245760 double-bit corruptions detected, and 2899556 checks in total.


Chapter 25.6 — Power Issues leaves the signal and looks at the rail. Its central claim has the same shape as this chapter's — that the useful information is in a split rather than a total — but the axis is time: the same voltage and the same current are a healthy device at one instant and a fault a second later, and the only thing that distinguishes them is how long ago it was plugged in.

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.