Skip to content
VLSI Mentor

USB · Module 25

Descriptor Issues

A descriptor set is described three times by three different fields, and the host walks it by one while reading it by another — so when they disagree the error lands on a field that is perfectly correct.

Chapter 25.1 ended on a device whose descriptor disagreed with itself. This chapter is about why that class of bug is so disproportionately painful to find.

1. A Descriptor Set Is a Self-Describing Tree

One buffer, containing a chain of variable-length records:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   offset 0   bLength           how long THIS record is
   offset 1   bDescriptorType   what it is
   ...        type-specific fields

   CONFIGURATION (type 2)   offset 2..3  wTotalLength
                            offset 4     bNumInterfaces
   INTERFACE     (type 4)   offset 4     bNumEndpoints
   ENDPOINT      (type 5)

The tree, and the three fields that describe it

A USB configuration descriptor tree. A configuration descriptor, which carries wTotalLength and bNumInterfaces, owns two interface descriptors. The first interface, which carries bNumEndpoints, owns two endpoint descriptors; the second interface owns one. Nothing in the buffer marks where each child list ends.CONFIGURATIONwTotalLength · bNumInterfacesINTERFACE 0bNumEndpoints = 2INTERFACE 1bNumEndpoints = 1ENDPOINT 0x81bulk INENDPOINT 0x02bulk OUTENDPOINT 0x83interrupt IN12
A configuration owns interfaces; an interface owns the endpoints that follow it. Nothing in the buffer marks where a child list ends — the structure is implied entirely by the order of the records and by three count fields that must agree with it.

The same buffer is therefore described three times, by three different fields, and all three must agree:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   wTotalLength    ==  the sum of every bLength in the set
   bNumInterfaces  ==  the number of INTERFACE records present
   bNumEndpoints   ==  the number of ENDPOINT records after each one

2. Why Disagreement Is So Confusing

The host asks for wTotalLength bytes, and then walks the buffer by adding bLength to a pointer. Those are two different fields doing two different jobs.

3. And bLength = 0 Is Not an Error, It Is a Hang

The walk advances by bLength. A record claiming zero length advances the pointer by zero, and the host reads the same record for ever.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ptr = 0
   while (ptr < wTotalLength) {
       len = buf[ptr];
       ...
       ptr += len;        <-- len == 0: ptr never moves
   }

A host that does not bound its descriptor walk hangs on a device that returns a zero bLength. Not fails — hangs.

Which is why this validator gives zero its own error class rather than folding it into "too short": a reader needs to know which symptom to expect, and the two symptoms are "a strange value" and "the machine stopped responding".

4. Short Is a Different Bug Again

bLength below the minimum for the type means the record cannot contain the fields the type is defined to have — so every field the host reads from it comes from the next record.

It is the most common single descriptor defect and the one that produces the strangest reports.

5. What We Are Building

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  usb_descriptor_validator  #(MIN_CFG=9, MIN_IF=9, MIN_EP=7, MIN_ANY=2)

  inputs                    outputs
  ------                    -------
  sod    start of a set     state / cur_type / cur_len
  byte_valid / byte_data    consumed vs total_len
  eod    the host stopped   if_seen vs if_expected
                            ep_seen vs ep_expected
                            err_code  ZEROLEN / SHORT / OVERRUN /
                                      TOTAL / IFCOUNT / EPCOUNT /
                                      ORPHAN
                            set_ok    one pulse per consistent set

  Every "seen vs expected" pair is exposed, because a report that
  says "bNumEndpoints is wrong" is much less useful than one that
  says "it says 3 and there are 2".

6. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_descriptor_validator -- a descriptor is a self-describing tree, and
// the three numbers that describe it must agree.
//
// THE SHAPE OF THE THING
//
// A configuration descriptor set is one buffer containing a chain of
// variable-length records:
//
//   offset 0   bLength           how long THIS record is
//   offset 1   bDescriptorType   what it is
//   ...        type-specific fields
//
//   CONFIGURATION (type 2)   offset 2..3  wTotalLength
//                            offset 4     bNumInterfaces
//   INTERFACE     (type 4)   offset 4     bNumEndpoints
//   ENDPOINT      (type 5)
//
// So the same buffer is described THREE times, by three different fields,
// and all three must agree:
//
//   wTotalLength      == the sum of every bLength in the set
//   bNumInterfaces    == the number of INTERFACE records present
//   bNumEndpoints     == the number of ENDPOINT records after each one
//
// HOW THE HOST READS IT, AND WHY DISAGREEMENT IS SO CONFUSING
//
// The host asks for wTotalLength bytes, and then walks the buffer by
// ADDING bLength to a pointer. Those are two different fields doing two
// different jobs, and when they disagree the host does not notice anything
// wrong at the point of disagreement -- it lands in the MIDDLE of the next
// record and reads its fields from the wrong offsets.
//
//     The error is reported against a field that is perfectly correct,
//     several records after the one that was wrong.
//
// That is why descriptor bugs are so disproportionately painful: the
// symptom and the cause are separated by a variable number of bytes, and
// the symptom is a plausible-looking value in an unrelated field.
//
// AND bLength = 0 IS NOT AN ERROR, IT IS A HANG
//
// The walk advances by bLength. A record claiming zero length advances the
// pointer by zero, and the host reads the same record for ever.
//
//     A host that does not bound its descriptor walk HANGS on a
//     device that returns a zero bLength. Not fails -- hangs.
//
// Which is why this validator treats a zero bLength as its own error class
// rather than folding it into "too short", and why it stops walking.
//
// A DESCRIPTOR SHORTER THAN ITS TYPE REQUIRES IS A DIFFERENT BUG
//
// bLength below the minimum for the type means the record cannot contain
// the fields the type is defined to have -- so every field the host reads
// from it comes from the NEXT record. It is the most common single
// descriptor defect and the one that produces the strangest reports.
module usb_descriptor_validator #(
  parameter integer MIN_CFG = 9,    // minimum bLength for CONFIGURATION
  parameter integer MIN_IF  = 9,    // ...for INTERFACE
  parameter integer MIN_EP  = 7,    // ...for ENDPOINT
  parameter integer MIN_ANY = 2     // ...for anything else: bLength + bType
) (
  input  wire        clk,
  input  wire        rst_n,

  input  wire        sod,          // start of a descriptor set
  input  wire        byte_valid,
  input  wire [7:0]  byte_data,
  input  wire        eod,          // the host stopped reading
  input  wire        eot,

  output wire [2:0]  state,
  output wire [7:0]  cur_type,
  output wire [7:0]  cur_len,
  output wire [15:0] consumed,     // bytes walked so far
  output wire [15:0] total_len,    // wTotalLength as the device claims it
  output wire [7:0]  if_seen,
  output wire [7:0]  if_expected,
  output wire [7:0]  ep_seen,      // endpoints after the CURRENT interface
  output wire [7:0]  ep_expected,
  output wire        err_pulse,
  output wire [2:0]  err_code,
  output wire        set_ok,       // one pulse per consistent descriptor set

  output reg [31:0]  n_desc,
  output reg [31:0]  n_zerolen,
  output reg [31:0]  n_short,
  output reg [31:0]  n_overrun,
  output reg [31:0]  n_total,
  output reg [31:0]  n_ifcount,
  output reg [31:0]  n_epcount,
  output reg [31:0]  n_orphan,
  output reg [31:0]  n_sets_ok
);

  localparam [2:0] W_IDLE = 3'd0,  // waiting for a descriptor set to start
                   W_LEN  = 3'd1,  // the next byte is bLength
                   W_TYPE = 3'd2,  // the next byte is bDescriptorType
                   W_BODY = 3'd3,  // consuming the rest of this record
                   W_DEAD = 3'd4;  // the walk cannot continue

  localparam [2:0] E_NONE    = 3'd0,
                   E_ZEROLEN = 3'd1,  // bLength 0: the walk cannot advance
                   E_SHORT   = 3'd2,  // shorter than the type requires
                   E_OVERRUN = 3'd3,  // the walk ran past wTotalLength
                   E_TOTAL   = 3'd4,  // wTotalLength != the sum of bLengths
                   E_IFCOUNT = 3'd5,  // bNumInterfaces != interfaces present
                   E_EPCOUNT = 3'd6,  // bNumEndpoints != endpoints present
                   E_ORPHAN  = 3'd7;  // an ENDPOINT with no INTERFACE above it

  localparam [7:0] T_CONFIG    = 8'd2,
                   T_INTERFACE = 8'd4,
                   T_ENDPOINT  = 8'd5;

  reg [2:0]  st_r;
  reg [7:0]  len_r, typ_r, off_r;
  reg [15:0] cons_r, total_r;
  reg [7:0]  ifs_r, ife_r, eps_r, epe_r;
  reg        have_if_r;     // an INTERFACE has been seen in this set
  reg        have_cfg_r;    // a CONFIGURATION has been seen
  reg [2:0]  ec_r;
  reg        er_r, ok_r;

  assign state       = st_r;
  assign cur_type    = typ_r;
  assign cur_len     = len_r;
  assign consumed    = cons_r;
  assign total_len   = total_r;
  assign if_seen     = ifs_r;
  assign if_expected = ife_r;
  assign ep_seen     = eps_r;
  assign ep_expected = epe_r;
  assign err_pulse   = er_r;
  assign err_code    = ec_r;
  assign set_ok      = ok_r;

  // The minimum bLength a record of this type can possibly have. A record
  // below it cannot contain the fields the type is DEFINED to have, so
  // every field read from it comes from the next record along.
  function [7:0] min_len;
    input [7:0] t;
    begin
      case (t)
        T_CONFIG:    min_len = MIN_CFG[7:0];
        T_INTERFACE: min_len = MIN_IF[7:0];
        T_ENDPOINT:  min_len = MIN_EP[7:0];
        default:     min_len = MIN_ANY[7:0];
      endcase
    end
  endfunction

  reg [2:0]  st_n, ec_n;
  reg [7:0]  len_n, typ_n, off_n;
  reg [15:0] cons_n, total_n;
  reg [7:0]  ifs_n, ife_n, eps_n, epe_n;
  reg        hif_n, hcfg_n, er_n, ok_n;

  always @* begin
    st_n    = st_r;
    len_n   = len_r;
    typ_n   = typ_r;
    off_n   = off_r;
    cons_n  = cons_r;
    total_n = total_r;
    ifs_n   = ifs_r;
    ife_n   = ife_r;
    eps_n   = eps_r;
    epe_n   = epe_r;
    hif_n   = have_if_r;
    hcfg_n  = have_cfg_r;
    ec_n    = E_NONE;
    er_n    = 1'b0;
    ok_n    = 1'b0;

    if (eot) begin
      st_n = W_IDLE;
    end else if (sod) begin
      // A fresh descriptor set. Everything the previous set accumulated is
      // gone: a stale interface count carried across a set boundary would
      // report a mismatch against a descriptor that is perfectly correct.
      st_n    = W_LEN;
      len_n   = 8'd0;
      typ_n   = 8'd0;
      off_n   = 8'd0;
      cons_n  = 16'd0;
      total_n = 16'd0;
      ifs_n   = 8'd0;
      ife_n   = 8'd0;
      eps_n   = 8'd0;
      epe_n   = 8'd0;
      hif_n   = 1'b0;
      hcfg_n  = 1'b0;
    end else if (eod) begin
      // ---- THE END OF THE SET. The aggregate checks happen HERE, and
      // ---- they are the ones that cannot be made record by record.
      //
      // W_DEAD is excluded deliberately. A walk that has already stopped
      // has already said why, and the aggregate counts it left behind are
      // wrong BECAUSE it stopped -- so re-checking them reports the same
      // defect a second time under a different name. One descriptor set,
      // one finding, which is chapter 23.4's rule applied to a buffer.
      if ((st_r != W_IDLE) && (st_r != W_DEAD)) begin
        if (hif_n && (eps_r != epe_r)) begin
          // The last interface's endpoint count. Every other interface was
          // checked when the NEXT one started; the last one has no next.
          er_n = 1'b1; ec_n = E_EPCOUNT;
        end else if (hcfg_n && (cons_r != total_r)) begin
          er_n = 1'b1; ec_n = E_TOTAL;
        end else if (hcfg_n && (ifs_r != ife_r)) begin
          er_n = 1'b1; ec_n = E_IFCOUNT;
        end else if (st_r == W_LEN) begin
          // Landed exactly on a record boundary with everything agreeing.
          ok_n = 1'b1;
        end else begin
          // The set ended in the middle of a record.
          er_n = 1'b1; ec_n = E_OVERRUN;
        end
      end
      st_n = W_IDLE;
    end else if (byte_valid) begin
      case (st_r)
        W_LEN: begin
          len_n  = byte_data;
          off_n  = 8'd1;
          cons_n = cons_r + 16'd1;
          if (byte_data == 8'd0) begin
            // ---- A ZERO LENGTH IS A HANG, NOT AN ERROR. ----
            //
            // The walk advances by bLength. Zero advances it by nothing, so
            // a host that does not bound its walk reads this record for
            // ever. Stopping is the only safe response, and saying so as
            // its own error class is what tells the reader that the symptom
            // will be a hang rather than a wrong value.
            er_n = 1'b1; ec_n = E_ZEROLEN;
            st_n = W_DEAD;
          end else begin
            st_n = W_TYPE;
          end
        end

        W_TYPE: begin
          typ_n  = byte_data;
          off_n  = 8'd2;
          cons_n = cons_r + 16'd1;

          if (len_r < min_len(byte_data)) begin
            // ---- Shorter than the type requires. ----
            //
            // The record cannot hold the fields its type is defined to
            // have, so every field the host reads from it comes from the
            // NEXT record. The walk stops, because continuing from here
            // produces a cascade of nonsense.
            er_n = 1'b1; ec_n = E_SHORT;
            st_n = W_DEAD;
          end else if (byte_data == T_ENDPOINT) begin
            if (!have_if_r) begin
              // An endpoint belongs to an interface. One that appears
              // before any interface has no owner, and the host will
              // attribute it to whatever interface comes next.
              er_n = 1'b1; ec_n = E_ORPHAN;
              st_n = W_DEAD;
            end else begin
              eps_n = eps_r + 8'd1;
              st_n  = (len_r == 8'd2) ? W_LEN : W_BODY;
            end
          end else if (byte_data == T_INTERFACE) begin
            // Close the PREVIOUS interface before opening this one.
            if (have_if_r && (eps_r != epe_r)) begin
              er_n = 1'b1; ec_n = E_EPCOUNT;
              st_n = W_DEAD;
            end else begin
              hif_n = 1'b1;
              ifs_n = ifs_r + 8'd1;
              eps_n = 8'd0;
              st_n  = (len_r == 8'd2) ? W_LEN : W_BODY;
            end
          end else begin
            if (byte_data == T_CONFIG) hcfg_n = 1'b1;
            st_n = (len_r == 8'd2) ? W_LEN : W_BODY;
          end
        end

        W_BODY: begin
          cons_n = cons_r + 16'd1;
          off_n  = off_r + 8'd1;

          // The type-specific fields, picked out by offset. This is the
          // only place the validator knows anything about what a
          // descriptor MEANS rather than how long it is.
          if (typ_r == T_CONFIG) begin
            if (off_r == 8'd2) total_n = {total_r[15:8], byte_data};
            if (off_r == 8'd3) total_n = {byte_data, total_r[7:0]};
            if (off_r == 8'd4) ife_n   = byte_data;
          end else if (typ_r == T_INTERFACE) begin
            if (off_r == 8'd4) epe_n = byte_data;
          end

          if (off_r + 8'd1 >= len_r) st_n = W_LEN;

          // ---- The walk must not run past what the device said. ----
          //
          // Checked as the bytes are consumed rather than at the end,
          // because by the end the pointer is somewhere meaningless and the
          // report would name whatever record it happened to land in.
          if (hcfg_n && (total_r != 16'd0) && (cons_n > total_r)) begin
            er_n = 1'b1; ec_n = E_OVERRUN;
            st_n = W_DEAD;
          end
        end

        default: begin
          // W_IDLE and W_DEAD: bytes are consumed and ignored. A dead walk
          // does not produce one error per remaining byte -- chapter 23.4's
          // rule, applied to a descriptor.
          cons_n = cons_r + 16'd1;
        end
      endcase
    end
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r       <= W_IDLE;
      len_r      <= 8'd0;
      typ_r      <= 8'd0;
      off_r      <= 8'd0;
      cons_r     <= 16'd0;
      total_r    <= 16'd0;
      ifs_r      <= 8'd0;
      ife_r      <= 8'd0;
      eps_r      <= 8'd0;
      epe_r      <= 8'd0;
      have_if_r  <= 1'b0;
      have_cfg_r <= 1'b0;
      ec_r       <= E_NONE;
      er_r       <= 1'b0;
      ok_r       <= 1'b0;
      n_desc     <= 32'd0;
      n_zerolen  <= 32'd0;
      n_short    <= 32'd0;
      n_overrun  <= 32'd0;
      n_total    <= 32'd0;
      n_ifcount  <= 32'd0;
      n_epcount  <= 32'd0;
      n_orphan   <= 32'd0;
      n_sets_ok  <= 32'd0;
    end else begin
      st_r       <= st_n;
      len_r      <= len_n;
      typ_r      <= typ_n;
      off_r      <= off_n;
      cons_r     <= cons_n;
      total_r    <= total_n;
      ifs_r      <= ifs_n;
      ife_r      <= ife_n;
      eps_r      <= eps_n;
      epe_r      <= epe_n;
      have_if_r  <= hif_n;
      have_cfg_r <= hcfg_n;
      ec_r       <= ec_n;
      er_r       <= er_n;
      ok_r       <= ok_n;

      if (byte_valid && (st_r == W_TYPE) && !sod && !eod && !eot)
                   n_desc    <= n_desc + 32'd1;
      if (ok_n)    n_sets_ok <= n_sets_ok + 32'd1;

      // The per-cause counters are driven by the SAME pulse as the error,
      // so they sum to it by construction (chapter 23.4).
      if (er_n) begin
        case (ec_n)
          E_ZEROLEN: n_zerolen <= n_zerolen + 32'd1;
          E_SHORT:   n_short   <= n_short   + 32'd1;
          E_OVERRUN: n_overrun <= n_overrun + 32'd1;
          E_TOTAL:   n_total   <= n_total   + 32'd1;
          E_IFCOUNT: n_ifcount <= n_ifcount + 32'd1;
          E_EPCOUNT: n_epcount <= n_epcount + 32'd1;
          E_ORPHAN:  n_orphan  <= n_orphan  + 32'd1;
          default: ;
        endcase
      end
    end
  end
endmodule

7. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_descriptor_validator -- a descriptor is a self-describing tree, and
// the three numbers that describe it must agree.
//
// THE SHAPE OF THE THING
//
// A configuration descriptor set is one buffer containing a chain of
// variable-length records:
//
//   offset 0   bLength           how long THIS record is
//   offset 1   bDescriptorType   what it is
//   ...        type-specific fields
//
//   CONFIGURATION (type 2)   offset 2..3  wTotalLength
//                            offset 4     bNumInterfaces
//   INTERFACE     (type 4)   offset 4     bNumEndpoints
//   ENDPOINT      (type 5)
//
// So the same buffer is described THREE times, by three different fields,
// and all three must agree:
//
//   wTotalLength      == the sum of every bLength in the set
//   bNumInterfaces    == the number of INTERFACE records present
//   bNumEndpoints     == the number of ENDPOINT records after each one
//
// HOW THE HOST READS IT, AND WHY DISAGREEMENT IS SO CONFUSING
//
// The host asks for wTotalLength bytes, and then walks the buffer by
// ADDING bLength to a pointer. Those are two different fields doing two
// different jobs, and when they disagree the host does not notice anything
// wrong at the point of disagreement -- it lands in the MIDDLE of the next
// record and reads its fields from the wrong offsets.
//
//     The error is reported against a field that is perfectly correct,
//     several records after the one that was wrong.
//
// That is why descriptor bugs are so disproportionately painful: the
// symptom and the cause are separated by a variable number of bytes, and
// the symptom is a plausible-looking value in an unrelated field.
//
// AND bLength = 0 IS NOT AN ERROR, IT IS A HANG
//
// The walk advances by bLength. A record claiming zero length advances the
// pointer by zero, and the host reads the same record for ever.
//
//     A host that does not bound its descriptor walk HANGS on a
//     device that returns a zero bLength. Not fails -- hangs.
//
// Which is why this validator treats a zero bLength as its own error class
// rather than folding it into "too short", and why it stops walking.
//
// A DESCRIPTOR SHORTER THAN ITS TYPE REQUIRES IS A DIFFERENT BUG
//
// bLength below the minimum for the type means the record cannot contain
// the fields the type is defined to have -- so every field the host reads
// from it comes from the NEXT record. It is the most common single
// descriptor defect and the one that produces the strangest reports.
package usb_desc_pkg;
  // The walker's states. W_DEAD exists so that a walk which has already
  // stopped does not report the same defect again under a different name
  // at the end of the buffer.
  typedef enum logic [2:0] {
    W_IDLE = 3'd0,   // waiting for a descriptor set to start
    W_LEN  = 3'd1,   // the next byte is bLength
    W_TYPE = 3'd2,   // the next byte is bDescriptorType
    W_BODY = 3'd3,   // consuming the rest of this record
    W_DEAD = 3'd4    // the walk cannot continue
  } walk_state_e;

  // E_ZEROLEN is separate from E_SHORT on purpose: a zero length is a HANG
  // and a short length is a wrong value, and a reader needs to know which
  // symptom to expect.
  typedef enum logic [2:0] {
    E_NONE    = 3'd0,
    E_ZEROLEN = 3'd1,   // bLength 0: the walk cannot advance
    E_SHORT   = 3'd2,   // shorter than the type requires
    E_OVERRUN = 3'd3,   // the walk ran past wTotalLength
    E_TOTAL   = 3'd4,   // wTotalLength != the sum of the bLengths
    E_IFCOUNT = 3'd5,   // bNumInterfaces != interfaces present
    E_EPCOUNT = 3'd6,   // bNumEndpoints != endpoints present
    E_ORPHAN  = 3'd7    // an ENDPOINT with no INTERFACE above it
  } desc_err_e;
endpackage

module usb_descriptor_validator
  import usb_desc_pkg::*;
 #(
  parameter int MIN_CFG = 9,    // minimum bLength for CONFIGURATION
  parameter int MIN_IF  = 9,    // ...for INTERFACE
  parameter int MIN_EP  = 7,    // ...for ENDPOINT
  parameter int MIN_ANY = 2     // ...for anything else: bLength + bType
) (
  input  logic       clk,
  input  logic       rst_n,

  input  logic       sod,          // start of a descriptor set
  input  logic       byte_valid,
  input  logic [7:0]  byte_data,
  input  logic       eod,          // the host stopped reading
  input  logic       eot,

  output walk_state_e state,
  output logic [7:0]  cur_type,
  output logic [7:0]  cur_len,
  output logic [15:0] consumed,     // bytes walked so far
  output logic [15:0] total_len,    // wTotalLength as the device claims it
  output logic [7:0]  if_seen,
  output logic [7:0]  if_expected,
  output logic [7:0]  ep_seen,      // endpoints after the CURRENT interface
  output logic [7:0]  ep_expected,
  output logic       err_pulse,
  output desc_err_e   err_code,
  output logic       set_ok,       // one pulse per consistent descriptor set

  output logic [31:0]  n_desc,
  output logic [31:0]  n_zerolen,
  output logic [31:0]  n_short,
  output logic [31:0]  n_overrun,
  output logic [31:0]  n_total,
  output logic [31:0]  n_ifcount,
  output logic [31:0]  n_epcount,
  output logic [31:0]  n_orphan,
  output logic [31:0]  n_sets_ok
);

  localparam [7:0] T_CONFIG    = 8'd2,
                   T_INTERFACE = 8'd4,
                   T_ENDPOINT  = 8'd5;

  walk_state_e st_r;
  desc_err_e   ec_r;
  logic [7:0]  len_r, typ_r, off_r;
  logic [15:0] cons_r, total_r;
  logic [7:0]  ifs_r, ife_r, eps_r, epe_r;
  logic        have_if_r;   // an INTERFACE has been seen in this set
  logic        have_cfg_r;  // a CONFIGURATION has been seen
  logic        er_r, ok_r;

  assign state       = st_r;
  assign cur_type    = typ_r;
  assign cur_len     = len_r;
  assign consumed    = cons_r;
  assign total_len   = total_r;
  assign if_seen     = ifs_r;
  assign if_expected = ife_r;
  assign ep_seen     = eps_r;
  assign ep_expected = epe_r;
  assign err_pulse   = er_r;
  assign err_code    = ec_r;
  assign set_ok      = ok_r;

  // The minimum bLength a record of this type can possibly have. A record
  // below it cannot contain the fields the type is DEFINED to have, so
  // every field read from it comes from the next record along.
  function automatic logic [7:0] min_len(input logic [7:0] t);
    begin
      case (t)
        T_CONFIG:    min_len = 8'(MIN_CFG);
        T_INTERFACE: min_len = 8'(MIN_IF);
        T_ENDPOINT:  min_len = 8'(MIN_EP);
        default:     min_len = 8'(MIN_ANY);
      endcase
    end
  endfunction

  walk_state_e st_n;
  desc_err_e   ec_n;
  logic [7:0]  len_n, typ_n, off_n;
  logic [15:0] cons_n, total_n;
  logic [7:0]  ifs_n, ife_n, eps_n, epe_n;
  logic        hif_n, hcfg_n, er_n, ok_n;

  always_comb begin
    st_n    = st_r;
    len_n   = len_r;
    typ_n   = typ_r;
    off_n   = off_r;
    cons_n  = cons_r;
    total_n = total_r;
    ifs_n   = ifs_r;
    ife_n   = ife_r;
    eps_n   = eps_r;
    epe_n   = epe_r;
    hif_n   = have_if_r;
    hcfg_n  = have_cfg_r;
    ec_n    = E_NONE;
    er_n    = 1'b0;
    ok_n    = 1'b0;

    if (eot) begin
      st_n = W_IDLE;
    end else if (sod) begin
      // A fresh descriptor set. Everything the previous set accumulated is
      // gone: a stale interface count carried across a set boundary would
      // report a mismatch against a descriptor that is perfectly correct.
      st_n    = W_LEN;
      len_n   = 8'd0;
      typ_n   = 8'd0;
      off_n   = 8'd0;
      cons_n  = 16'd0;
      total_n = 16'd0;
      ifs_n   = 8'd0;
      ife_n   = 8'd0;
      eps_n   = 8'd0;
      epe_n   = 8'd0;
      hif_n   = 1'b0;
      hcfg_n  = 1'b0;
    end else if (eod) begin
      // ---- THE END OF THE SET. The aggregate checks happen HERE, and
      // ---- they are the ones that cannot be made record by record.
      //
      // W_DEAD is excluded deliberately. A walk that has already stopped
      // has already said why, and the aggregate counts it left behind are
      // wrong BECAUSE it stopped -- so re-checking them reports the same
      // defect a second time under a different name. One descriptor set,
      // one finding, which is chapter 23.4's rule applied to a buffer.
      if ((st_r != W_IDLE) && (st_r != W_DEAD)) begin
        if (hif_n && (eps_r != epe_r)) begin
          // The last interface's endpoint count. Every other interface was
          // checked when the NEXT one started; the last one has no next.
          er_n = 1'b1; ec_n = E_EPCOUNT;
        end else if (hcfg_n && (cons_r != total_r)) begin
          er_n = 1'b1; ec_n = E_TOTAL;
        end else if (hcfg_n && (ifs_r != ife_r)) begin
          er_n = 1'b1; ec_n = E_IFCOUNT;
        end else if (st_r == W_LEN) begin
          // Landed exactly on a record boundary with everything agreeing.
          ok_n = 1'b1;
        end else begin
          // The set ended in the middle of a record.
          er_n = 1'b1; ec_n = E_OVERRUN;
        end
      end
      st_n = W_IDLE;
    end else if (byte_valid) begin
      case (st_r)
        W_LEN: begin
          len_n  = byte_data;
          off_n  = 8'd1;
          cons_n = cons_r + 16'd1;
          if (byte_data == 8'd0) begin
            // ---- A ZERO LENGTH IS A HANG, NOT AN ERROR. ----
            //
            // The walk advances by bLength. Zero advances it by nothing, so
            // a host that does not bound its walk reads this record for
            // ever. Stopping is the only safe response, and saying so as
            // its own error class is what tells the reader that the symptom
            // will be a hang rather than a wrong value.
            er_n = 1'b1; ec_n = E_ZEROLEN;
            st_n = W_DEAD;
          end else begin
            st_n = W_TYPE;
          end
        end

        W_TYPE: begin
          typ_n  = byte_data;
          off_n  = 8'd2;
          cons_n = cons_r + 16'd1;

          if (len_r < min_len(byte_data)) begin
            // ---- Shorter than the type requires. ----
            //
            // The record cannot hold the fields its type is defined to
            // have, so every field the host reads from it comes from the
            // NEXT record. The walk stops, because continuing from here
            // produces a cascade of nonsense.
            er_n = 1'b1; ec_n = E_SHORT;
            st_n = W_DEAD;
          end else if (byte_data == T_ENDPOINT) begin
            if (!have_if_r) begin
              // An endpoint belongs to an interface. One that appears
              // before any interface has no owner, and the host will
              // attribute it to whatever interface comes next.
              er_n = 1'b1; ec_n = E_ORPHAN;
              st_n = W_DEAD;
            end else begin
              eps_n = eps_r + 8'd1;
              // Written as if/else rather than a ternary: an enum-valued
              // ternary needs an explicit cast in Icarus.
              if (len_r == 8'd2) st_n = W_LEN; else st_n = W_BODY;
            end
          end else if (byte_data == T_INTERFACE) begin
            // Close the PREVIOUS interface before opening this one.
            if (have_if_r && (eps_r != epe_r)) begin
              er_n = 1'b1; ec_n = E_EPCOUNT;
              st_n = W_DEAD;
            end else begin
              hif_n = 1'b1;
              ifs_n = ifs_r + 8'd1;
              eps_n = 8'd0;
              // Written as if/else rather than a ternary: an enum-valued
              // ternary needs an explicit cast in Icarus.
              if (len_r == 8'd2) st_n = W_LEN; else st_n = W_BODY;
            end
          end else begin
            if (byte_data == T_CONFIG) hcfg_n = 1'b1;
            if (len_r == 8'd2) st_n = W_LEN; else st_n = W_BODY;
          end
        end

        W_BODY: begin
          cons_n = cons_r + 16'd1;
          off_n  = off_r + 8'd1;

          // The type-specific fields, picked out by offset. This is the
          // only place the validator knows anything about what a
          // descriptor MEANS rather than how long it is.
          if (typ_r == T_CONFIG) begin
            if (off_r == 8'd2) total_n = {total_r[15:8], byte_data};
            if (off_r == 8'd3) total_n = {byte_data, total_r[7:0]};
            if (off_r == 8'd4) ife_n   = byte_data;
          end else if (typ_r == T_INTERFACE) begin
            if (off_r == 8'd4) epe_n = byte_data;
          end

          if (off_r + 8'd1 >= len_r) st_n = W_LEN;

          // ---- The walk must not run past what the device said. ----
          //
          // Checked as the bytes are consumed rather than at the end,
          // because by the end the pointer is somewhere meaningless and the
          // report would name whatever record it happened to land in.
          if (hcfg_n && (total_r != 16'd0) && (cons_n > total_r)) begin
            er_n = 1'b1; ec_n = E_OVERRUN;
            st_n = W_DEAD;
          end
        end

        default: begin
          // W_IDLE and W_DEAD: bytes are consumed and ignored. A dead walk
          // does not produce one error per remaining byte -- chapter 23.4's
          // rule, applied to a descriptor.
          cons_n = cons_r + 16'd1;
        end
      endcase
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r       <= W_IDLE;
      len_r      <= 8'd0;
      typ_r      <= 8'd0;
      off_r      <= 8'd0;
      cons_r     <= 16'd0;
      total_r    <= 16'd0;
      ifs_r      <= 8'd0;
      ife_r      <= 8'd0;
      eps_r      <= 8'd0;
      epe_r      <= 8'd0;
      have_if_r  <= 1'b0;
      have_cfg_r <= 1'b0;
      ec_r       <= E_NONE;
      er_r       <= 1'b0;
      ok_r       <= 1'b0;
      n_desc     <= 32'd0;
      n_zerolen  <= 32'd0;
      n_short    <= 32'd0;
      n_overrun  <= 32'd0;
      n_total    <= 32'd0;
      n_ifcount  <= 32'd0;
      n_epcount  <= 32'd0;
      n_orphan   <= 32'd0;
      n_sets_ok  <= 32'd0;
    end else begin
      st_r       <= st_n;
      len_r      <= len_n;
      typ_r      <= typ_n;
      off_r      <= off_n;
      cons_r     <= cons_n;
      total_r    <= total_n;
      ifs_r      <= ifs_n;
      ife_r      <= ife_n;
      eps_r      <= eps_n;
      epe_r      <= epe_n;
      have_if_r  <= hif_n;
      have_cfg_r <= hcfg_n;
      ec_r       <= ec_n;
      er_r       <= er_n;
      ok_r       <= ok_n;

      if (byte_valid && (st_r == W_TYPE) && !sod && !eod && !eot)
                   n_desc    <= n_desc + 32'd1;
      if (ok_n)    n_sets_ok <= n_sets_ok + 32'd1;

      // The per-cause counters are driven by the SAME pulse as the error,
      // so they sum to it by construction (chapter 23.4).
      if (er_n) begin
        case (ec_n)
          E_ZEROLEN: n_zerolen <= n_zerolen + 32'd1;
          E_SHORT:   n_short   <= n_short   + 32'd1;
          E_OVERRUN: n_overrun <= n_overrun + 32'd1;
          E_TOTAL:   n_total   <= n_total   + 32'd1;
          E_IFCOUNT: n_ifcount <= n_ifcount + 32'd1;
          E_EPCOUNT: n_epcount <= n_epcount + 32'd1;
          E_ORPHAN:  n_orphan  <= n_orphan  + 32'd1;
          default: ;
        endcase
      end
    end
  end
endmodule

8. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb_descriptor_validator -- a descriptor is a self-describing tree, and
-- the three numbers that describe it must agree.
--
-- THE SHAPE OF THE THING
--
-- A configuration descriptor set is one buffer containing a chain of
-- variable-length records:
--
--   offset 0   bLength           how long THIS record is
--   offset 1   bDescriptorType   what it is
--   ...        type-specific fields
--
--   CONFIGURATION (type 2)   offset 2..3  wTotalLength
--                            offset 4     bNumInterfaces
--   INTERFACE     (type 4)   offset 4     bNumEndpoints
--   ENDPOINT      (type 5)
--
-- So the same buffer is described THREE times, by three different fields,
-- and all three must agree:
--
--   wTotalLength      == the sum of every bLength in the set
--   bNumInterfaces    == the number of INTERFACE records present
--   bNumEndpoints     == the number of ENDPOINT records after each one
--
-- HOW THE HOST READS IT, AND WHY DISAGREEMENT IS SO CONFUSING
--
-- The host asks for wTotalLength bytes, and then walks the buffer by
-- ADDING bLength to a pointer. Those are two different fields doing two
-- different jobs, and when they disagree the host does not notice anything
-- wrong at the point of disagreement -- it lands in the MIDDLE of the next
-- record and reads its fields from the wrong offsets.
--
--     The error is reported against a field that is perfectly correct,
--     several records after the one that was wrong.
--
-- That is why descriptor bugs are so disproportionately painful: the
-- symptom and the cause are separated by a variable number of bytes, and
-- the symptom is a plausible-looking value in an unrelated field.
--
-- AND bLength = 0 IS NOT AN ERROR, IT IS A HANG
--
-- The walk advances by bLength. A record claiming zero length advances the
-- pointer by zero, and the host reads the same record for ever.
--
--     A host that does not bound its descriptor walk HANGS on a
--     device that returns a zero bLength. Not fails -- hangs.
--
-- Which is why this validator treats a zero bLength as its own error class
-- rather than folding it into "too short", and why it stops walking.
--
-- A DESCRIPTOR SHORTER THAN ITS TYPE REQUIRES IS A DIFFERENT BUG
--
-- bLength below the minimum for the type means the record cannot contain
-- the fields the type is defined to have -- so every field the host reads
-- from it comes from the NEXT record. It is the most common single
-- descriptor defect and the one that produces the strangest reports.
library ieee;
use ieee.std_logic_1164.all;

package usb_desc_pkg is
  -- The walker's states. W_DEAD exists so that a walk which has already
  -- stopped does not report the same defect again under a different name
  -- at the end of the buffer.
  type walk_state_t is (W_IDLE, W_LEN, W_TYPE, W_BODY, W_DEAD);

  -- E_ZEROLEN is separate from E_SHORT on purpose: a zero length is a HANG
  -- and a short length is a wrong value, and a reader needs to know which
  -- symptom to expect.
  type desc_err_t is (E_NONE, E_ZEROLEN, E_SHORT, E_OVERRUN, E_TOTAL,
                      E_IFCOUNT, E_EPCOUNT, E_ORPHAN);

  function w_code (w : walk_state_t) return std_logic_vector;
  function e_code (e : desc_err_t)   return std_logic_vector;
end package usb_desc_pkg;

package body usb_desc_pkg is
  function w_code (w : walk_state_t) return std_logic_vector is
  begin
    case w is
      when W_IDLE => return "000";
      when W_LEN  => return "001";
      when W_TYPE => return "010";
      when W_BODY => return "011";
      when W_DEAD => return "100";
    end case;
  end function;

  function e_code (e : desc_err_t) return std_logic_vector is
  begin
    case e is
      when E_NONE    => return "000";
      when E_ZEROLEN => return "001";
      when E_SHORT   => return "010";
      when E_OVERRUN => return "011";
      when E_TOTAL   => return "100";
      when E_IFCOUNT => return "101";
      when E_EPCOUNT => return "110";
      when E_ORPHAN  => return "111";
    end case;
  end function;
end package body usb_desc_pkg;

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

entity usb_descriptor_validator is
  generic (
    MIN_CFG : integer := 9;   -- minimum bLength for CONFIGURATION
    MIN_IF  : integer := 9;   -- ...for INTERFACE
    MIN_EP  : integer := 7;   -- ...for ENDPOINT
    MIN_ANY : integer := 2    -- ...for anything else: bLength + bType
  );
  port (
    clk         : in  std_logic;
    rst_n       : in  std_logic;

    sod         : in  std_logic;                     -- start of a set
    byte_valid  : in  std_logic;
    byte_data   : in  std_logic_vector(7 downto 0);
    eod         : in  std_logic;                     -- the host stopped
    eot         : in  std_logic;

    state       : out std_logic_vector(2 downto 0);
    cur_type    : out std_logic_vector(7 downto 0);
    cur_len     : out std_logic_vector(7 downto 0);
    consumed    : out std_logic_vector(15 downto 0);
    total_len   : out std_logic_vector(15 downto 0);
    if_seen     : out std_logic_vector(7 downto 0);
    if_expected : out std_logic_vector(7 downto 0);
    ep_seen     : out std_logic_vector(7 downto 0);
    ep_expected : out std_logic_vector(7 downto 0);
    err_pulse   : out std_logic;
    err_code    : out std_logic_vector(2 downto 0);
    set_ok      : out std_logic;

    n_desc      : out std_logic_vector(31 downto 0);
    n_zerolen   : out std_logic_vector(31 downto 0);
    n_short     : out std_logic_vector(31 downto 0);
    n_overrun   : out std_logic_vector(31 downto 0);
    n_total     : out std_logic_vector(31 downto 0);
    n_ifcount   : out std_logic_vector(31 downto 0);
    n_epcount   : out std_logic_vector(31 downto 0);
    n_orphan    : out std_logic_vector(31 downto 0);
    n_sets_ok   : out std_logic_vector(31 downto 0)
  );
end entity usb_descriptor_validator;

architecture rtl of usb_descriptor_validator is

  constant T_CONFIG    : std_logic_vector(7 downto 0) := x"02";
  constant T_INTERFACE : std_logic_vector(7 downto 0) := x"04";
  constant T_ENDPOINT  : std_logic_vector(7 downto 0) := x"05";

  signal st_r  : walk_state_t := W_IDLE;
  signal ec_r  : desc_err_t   := E_NONE;
  signal len_r, typ_r, off_r : unsigned(7 downto 0) := (others => '0');
  signal cons_r, total_r : unsigned(15 downto 0) := (others => '0');
  signal ifs_r, ife_r, eps_r, epe_r : unsigned(7 downto 0) := (others => '0');
  signal have_if_r, have_cfg_r : std_logic := '0';
  signal er_r, ok_r : std_logic := '0';

  -- The minimum bLength a record of this type can possibly have. A record
  -- below it cannot contain the fields the type is DEFINED to have, so
  -- every field read from it comes from the next record along.
  function min_len (t : std_logic_vector(7 downto 0)) return unsigned is
  begin
    if t = T_CONFIG then
      return to_unsigned(MIN_CFG, 8);
    elsif t = T_INTERFACE then
      return to_unsigned(MIN_IF, 8);
    elsif t = T_ENDPOINT then
      return to_unsigned(MIN_EP, 8);
    else
      return to_unsigned(MIN_ANY, 8);
    end if;
  end function;

  -- Accumulators are held as unsigned rather than as range-constrained
  -- integers: a constrained integer aborts simulation on overflow, which
  -- turns a mutation into a crash instead of a measured kill.
  signal c_d, c_zl, c_sh, c_ov, c_to : unsigned(31 downto 0) := (others => '0');
  signal c_ic, c_ep, c_or, c_ok      : unsigned(31 downto 0) := (others => '0');

begin

  state       <= w_code(st_r);
  cur_type    <= std_logic_vector(typ_r);
  cur_len     <= std_logic_vector(len_r);
  consumed    <= std_logic_vector(cons_r);
  total_len   <= std_logic_vector(total_r);
  if_seen     <= std_logic_vector(ifs_r);
  if_expected <= std_logic_vector(ife_r);
  ep_seen     <= std_logic_vector(eps_r);
  ep_expected <= std_logic_vector(epe_r);
  err_pulse   <= er_r;
  err_code    <= e_code(ec_r);
  set_ok      <= ok_r;

  n_desc    <= std_logic_vector(c_d);
  n_zerolen <= std_logic_vector(c_zl);
  n_short   <= std_logic_vector(c_sh);
  n_overrun <= std_logic_vector(c_ov);
  n_total   <= std_logic_vector(c_to);
  n_ifcount <= std_logic_vector(c_ic);
  n_epcount <= std_logic_vector(c_ep);
  n_orphan  <= std_logic_vector(c_or);
  n_sets_ok <= std_logic_vector(c_ok);

  process (clk, rst_n)
    variable ns   : walk_state_t;
    variable nec  : desc_err_t;
    variable nlen, ntyp, noff : unsigned(7 downto 0);
    variable ncons, ntot : unsigned(15 downto 0);
    variable nifs, nife, neps, nepe : unsigned(7 downto 0);
    variable nhif, nhcfg, ner, nok : std_logic;
    variable bd : unsigned(7 downto 0);
  begin
    if rst_n = '0' then
      st_r <= W_IDLE; ec_r <= E_NONE;
      len_r <= (others => '0'); typ_r <= (others => '0');
      off_r <= (others => '0');
      cons_r <= (others => '0'); total_r <= (others => '0');
      ifs_r <= (others => '0'); ife_r <= (others => '0');
      eps_r <= (others => '0'); epe_r <= (others => '0');
      have_if_r <= '0'; have_cfg_r <= '0';
      er_r <= '0'; ok_r <= '0';
      c_d  <= (others => '0'); c_zl <= (others => '0');
      c_sh <= (others => '0'); c_ov <= (others => '0');
      c_to <= (others => '0'); c_ic <= (others => '0');
      c_ep <= (others => '0'); c_or <= (others => '0');
      c_ok <= (others => '0');
    elsif rising_edge(clk) then
      bd    := unsigned(byte_data);
      ns    := st_r;  nlen := len_r;  ntyp := typ_r;  noff := off_r;
      ncons := cons_r; ntot := total_r;
      nifs  := ifs_r; nife := ife_r; neps := eps_r; nepe := epe_r;
      nhif  := have_if_r; nhcfg := have_cfg_r;
      nec   := E_NONE; ner := '0'; nok := '0';

      if eot = '1' then
        ns := W_IDLE;
      elsif sod = '1' then
        -- A fresh descriptor set. Everything the previous set accumulated is
        -- gone: a stale interface count carried across a set boundary would
        -- report a mismatch against a descriptor that is perfectly correct.
        ns := W_LEN;
        nlen := (others => '0'); ntyp := (others => '0');
        noff := (others => '0');
        ncons := (others => '0'); ntot := (others => '0');
        nifs := (others => '0'); nife := (others => '0');
        neps := (others => '0'); nepe := (others => '0');
        nhif := '0'; nhcfg := '0';
      elsif eod = '1' then
        -- ---- THE END OF THE SET. The aggregate checks happen HERE, and
        -- ---- they are the ones that cannot be made record by record.
        --
        -- W_DEAD is excluded deliberately. A walk that has already stopped
        -- has already said why, and the aggregate counts it left behind are
        -- wrong BECAUSE it stopped -- so re-checking them reports the same
        -- defect a second time under a different name. One descriptor set,
        -- one finding, which is chapter 23.4's rule applied to a buffer.
        if st_r /= W_IDLE and st_r /= W_DEAD then
          if nhif = '1' and eps_r /= epe_r then
            -- The last interface's endpoint count. Every other interface was
            -- checked when the NEXT one started; the last one has no next.
            ner := '1'; nec := E_EPCOUNT;
          elsif nhcfg = '1' and cons_r /= total_r then
            ner := '1'; nec := E_TOTAL;
          elsif nhcfg = '1' and ifs_r /= ife_r then
            ner := '1'; nec := E_IFCOUNT;
          elsif st_r = W_LEN then
            -- Landed exactly on a record boundary with everything agreeing.
            nok := '1';
          else
            -- The set ended in the middle of a record.
            ner := '1'; nec := E_OVERRUN;
          end if;
        end if;
        ns := W_IDLE;
      elsif byte_valid = '1' then
        case st_r is
          when W_LEN =>
            nlen := bd; noff := to_unsigned(1, 8); ncons := cons_r + 1;
            if bd = 0 then
              -- ---- A ZERO LENGTH IS A HANG, NOT AN ERROR. ----
              --
              -- The walk advances by bLength. Zero advances it by nothing,
              -- so a host that does not bound its walk reads this record
              -- for ever. Stopping is the only safe response, and saying so
              -- as its own error class is what tells the reader that the
              -- symptom will be a hang rather than a wrong value.
              ner := '1'; nec := E_ZEROLEN; ns := W_DEAD;
            else
              ns := W_TYPE;
            end if;

          when W_TYPE =>
            ntyp := bd; noff := to_unsigned(2, 8); ncons := cons_r + 1;
            if len_r < min_len(byte_data) then
              -- ---- Shorter than the type requires. ----
              ner := '1'; nec := E_SHORT; ns := W_DEAD;
            elsif byte_data = T_ENDPOINT then
              if have_if_r = '0' then
                -- An endpoint belongs to an interface. One that appears
                -- before any interface has no owner, and the host will
                -- attribute it to whatever interface comes next.
                ner := '1'; nec := E_ORPHAN; ns := W_DEAD;
              else
                neps := eps_r + 1;
                if len_r = 2 then ns := W_LEN; else ns := W_BODY; end if;
              end if;
            elsif byte_data = T_INTERFACE then
              -- Close the PREVIOUS interface before opening this one.
              if have_if_r = '1' and eps_r /= epe_r then
                ner := '1'; nec := E_EPCOUNT; ns := W_DEAD;
              else
                nhif := '1'; nifs := ifs_r + 1; neps := (others => '0');
                if len_r = 2 then ns := W_LEN; else ns := W_BODY; end if;
              end if;
            else
              if byte_data = T_CONFIG then nhcfg := '1'; end if;
              if len_r = 2 then ns := W_LEN; else ns := W_BODY; end if;
            end if;

          when W_BODY =>
            ncons := cons_r + 1; noff := off_r + 1;
            -- The type-specific fields, picked out by offset. This is the
            -- only place the validator knows anything about what a
            -- descriptor MEANS rather than how long it is.
            if typ_r = unsigned(T_CONFIG) then
              if off_r = 2 then ntot := total_r(15 downto 8) & bd; end if;
              if off_r = 3 then ntot := bd & total_r(7 downto 0); end if;
              if off_r = 4 then nife := bd; end if;
            elsif typ_r = unsigned(T_INTERFACE) then
              if off_r = 4 then nepe := bd; end if;
            end if;

            if off_r + 1 >= len_r then ns := W_LEN; end if;

            -- ---- The walk must not run past what the device said. ----
            --
            -- Checked as the bytes are consumed rather than at the end,
            -- because by the end the pointer is somewhere meaningless and
            -- the report would name whatever record it happened to land in.
            if nhcfg = '1' and total_r /= 0 and ncons > total_r then
              ner := '1'; nec := E_OVERRUN; ns := W_DEAD;
            end if;

          when others =>
            -- W_IDLE and W_DEAD: bytes are consumed and ignored. A dead walk
            -- does not produce one error per remaining byte -- chapter
            -- 23.4's rule, applied to a descriptor.
            ncons := cons_r + 1;
        end case;
      end if;

      if byte_valid = '1' and st_r = W_TYPE and sod = '0'
         and eod = '0' and eot = '0' then
        c_d <= c_d + 1;
      end if;

      st_r <= ns; len_r <= nlen; typ_r <= ntyp; off_r <= noff;
      cons_r <= ncons; total_r <= ntot;
      ifs_r <= nifs; ife_r <= nife; eps_r <= neps; epe_r <= nepe;
      have_if_r <= nhif; have_cfg_r <= nhcfg;
      ec_r <= nec; er_r <= ner; ok_r <= nok;

      if nok = '1' then c_ok <= c_ok + 1; end if;

      -- The per-cause counters are driven by the SAME pulse as the error,
      -- so they sum to it by construction (chapter 23.4).
      if ner = '1' then
        case nec is
          when E_ZEROLEN => c_zl <= c_zl + 1;
          when E_SHORT   => c_sh <= c_sh + 1;
          when E_OVERRUN => c_ov <= c_ov + 1;
          when E_TOTAL   => c_to <= c_to + 1;
          when E_IFCOUNT => c_ic <= c_ic + 1;
          when E_EPCOUNT => c_ep <= c_ep + 1;
          when E_ORPHAN  => c_or <= c_or + 1;
          when others    => null;
        end case;
      end if;
    end if;
  end process;

end architecture rtl;

9. Seeing the Walk Stop

A two-byte record, then a record claiming zero length

usb_descriptor_validator — a zero bLength stops the walk

10 cycles
A ten-cycle waveform. A start-of-set pulse puts the walker into the length state. It reads bLength equal to 2, then a descriptor type, completing a two-byte record and returning to the length state. The next byte is zero, which raises err_pulse with code ZEROLEN and puts the walker into the dead state. Subsequent bytes are consumed without further reports, and end-of-data returns it to idle.bLength = 2: a complete recordbLength = 2: a completerecordbLength = 0: cannot advancebLength = 0: cannot advanceZEROLEN, and the walk stopsZEROLEN, and the walk stopsdead: consumed, nothing reporteddead: consumed, nothingreportedclksodbyte_validbyte_data002030000AABBBBBBBBeodstateIDLELENTYPELENDEADDEADDEADDEADDEADIDLEcur_len0022000000consumed0012334555err_pulset0t1t2t3t4t5t6t7t8t9
The walker reads bLength and bDescriptorType, finds a complete two-byte record, and returns to expecting a length. The next byte is zero — a length the walk cannot advance past — so it is reported as ZEROLEN and the walk stops. The bytes after it are consumed and nothing further is reported.

10. The Testbenches

Two exhaustive sweeps, and the first is the one worth reading:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ALL 256 VALUES of bLength on the first record of a set,
   with the type fixed at CONFIGURATION.

   The validator must reject exactly three populations:

       bLength == 0            ZEROLEN  -- a hang
       0 < bLength < MIN_CFG   SHORT    -- a wrong value
       everything else         accepted

   and it must reject zero with a DIFFERENT code, because a
   reader needs to know which symptom to expect.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      if (L == 0) begin
        check(n_zerolen == b_e + 1,
              "bLength zero was not reported as a zero length");
        check(n_short == b_ok,
              "bLength zero was classified as SHORT -- zero is a hang and short is a wrong value, and a reader needs to know which symptom to expect");
      end else if (L < MIN_CFG) begin
        check(n_short == b_ok + 1,
              "a CONFIGURATION below its minimum length was accepted");
        check(n_zerolen == b_e, "a short length was classified as zero");
      end else begin
        check(n_zerolen == b_e && n_short == b_ok,
              "a bLength at or above the type's minimum was rejected");
      end

And the false-positive half — 180 perfectly consistent sets of three different shapes, including the two that a careless validator rejects: an interface with no endpoints at all, and a set with two interfaces.

10.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Testbench for usb_descriptor_validator (Verilog-2005).
//
// WHAT IS EXHAUSTIVE HERE
//
//   1. ALL 256 VALUES of bLength on the first record of a set, with the
//      type fixed at CONFIGURATION. The validator must reject exactly
//      three populations -- zero, everything below the type's minimum, and
//      nothing else -- and it must reject zero with a DIFFERENT code,
//      because zero is a hang and short is a wrong value.
//
//   2. Every walker state crossed with all eight combinations of
//      {byte_valid, sod, eod} = 40 pairs, each state reached by real bytes.
//
// AND THE SET THE CHAPTER IS ABOUT
//
// A descriptor set whose wTotalLength disagrees with the sum of its
// bLengths by ONE byte. The host reads the right number of bytes and lands
// one byte into the next record, so every field it reads after that point
// comes from the wrong offset -- and the error it eventually reports is
// against a field that is perfectly correct.
`timescale 1ns/1ps
module tb_dv_v;

  localparam integer MIN_CFG = 9;
  localparam integer MIN_IF  = 9;
  localparam integer MIN_EP  = 7;
  localparam integer MIN_ANY = 2;

  localparam [2:0] W_IDLE=3'd0, W_LEN=3'd1, W_TYPE=3'd2, W_BODY=3'd3, W_DEAD=3'd4;
  localparam [2:0] E_NONE=3'd0, E_ZEROLEN=3'd1, E_SHORT=3'd2, E_OVERRUN=3'd3,
                   E_TOTAL=3'd4, E_IFCOUNT=3'd5, E_EPCOUNT=3'd6, E_ORPHAN=3'd7;

  localparam [7:0] T_DEVICE=8'd1, T_CONFIG=8'd2, T_STRING=8'd3,
                   T_INTERFACE=8'd4, T_ENDPOINT=8'd5;

  reg clk = 1'b0, rst_n = 1'b0;
  reg sod = 1'b0, byte_valid = 1'b0, eod = 1'b0, eot = 1'b0;
  reg [7:0] byte_data = 8'd0;

  wire [2:0]  state, err_code;
  wire [7:0]  cur_type, cur_len, if_seen, if_expected, ep_seen, ep_expected;
  wire [15:0] consumed, total_len;
  wire err_pulse, set_ok;
  wire [31:0] n_desc, n_zerolen, n_short, n_overrun, n_total,
              n_ifcount, n_epcount, n_orphan, n_sets_ok;

  usb_descriptor_validator #(.MIN_CFG(MIN_CFG), .MIN_IF(MIN_IF),
                             .MIN_EP(MIN_EP), .MIN_ANY(MIN_ANY)) dut (
    .clk(clk), .rst_n(rst_n),
    .sod(sod), .byte_valid(byte_valid), .byte_data(byte_data),
    .eod(eod), .eot(eot),
    .state(state), .cur_type(cur_type), .cur_len(cur_len),
    .consumed(consumed), .total_len(total_len),
    .if_seen(if_seen), .if_expected(if_expected),
    .ep_seen(ep_seen), .ep_expected(ep_expected),
    .err_pulse(err_pulse), .err_code(err_code), .set_ok(set_ok),
    .n_desc(n_desc), .n_zerolen(n_zerolen), .n_short(n_short),
    .n_overrun(n_overrun), .n_total(n_total), .n_ifcount(n_ifcount),
    .n_epcount(n_epcount), .n_orphan(n_orphan), .n_sets_ok(n_sets_ok)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0;
  task check(input cond, input [1023:0] msg);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 25)
          $display("FAIL @%0t: %0s | st=%0d typ=%0d len=%0d cons=%0d tot=%0d if=%0d/%0d ep=%0d/%0d e=%b(%0d)",
                   $time, msg, state, cur_type, cur_len, consumed, total_len,
                   if_seen, if_expected, ep_seen, ep_expected, err_pulse, err_code);
      end
    end
  endtask

  // ------------------------------------------------------------------
  // The shadow walker. Written from the descriptor format, not the design.
  // ------------------------------------------------------------------
  reg [2:0]  m_st, m_ec;
  reg [7:0]  m_len, m_typ, m_off, m_ifs, m_ife, m_eps, m_epe;
  reg [15:0] m_cons, m_tot;
  reg        m_hif, m_hcfg, m_er, m_ok;
  integer    m_d, m_zl, m_sh, m_ov, m_to, m_ic, m_ep, m_or, m_sok;

  integer seen_len [0:255];     // all 256 bLength values on record 0
  integer seen_si  [0:39];      // 5 states x 8 input combinations
  integer n_len, n_si, n_steps;

  task model_reset;
    integer j;
    begin
      m_st = W_IDLE; m_ec = E_NONE;
      m_len = 8'd0; m_typ = 8'd0; m_off = 8'd0;
      m_ifs = 8'd0; m_ife = 8'd0; m_eps = 8'd0; m_epe = 8'd0;
      m_cons = 16'd0; m_tot = 16'd0;
      m_hif = 1'b0; m_hcfg = 1'b0; m_er = 1'b0; m_ok = 1'b0;
      m_d = 0; m_zl = 0; m_sh = 0; m_ov = 0; m_to = 0;
      m_ic = 0; m_ep = 0; m_or = 0; m_sok = 0;
      for (j = 0; j < 256; j = j + 1) seen_len[j] = 0;
      for (j = 0; j < 40;  j = j + 1) seen_si[j]  = 0;
      n_len = 0; n_si = 0; n_steps = 0;
    end
  endtask

  function [7:0] min_len;
    input [7:0] t;
    begin
      if (t == T_CONFIG)         min_len = MIN_CFG[7:0];
      else if (t == T_INTERFACE) min_len = MIN_IF[7:0];
      else if (t == T_ENDPOINT)  min_len = MIN_EP[7:0];
      else                       min_len = MIN_ANY[7:0];
    end
  endfunction

  integer idx;
  task step(input sd, input bv, input [7:0] bd, input ed, input eo);
    reg [2:0]  ns, nec;
    reg [7:0]  nlen, ntyp, noff, nifs, nife, neps, nepe;
    reg [15:0] ncons, ntot;
    reg        nhif, nhcfg, ner, nok;
    begin
      sod = sd; byte_valid = bv; byte_data = bd; eod = ed; eot = eo;
      #1;

      check(state       === m_st,   "state disagrees with the shadow walker");
      check(cur_type    === m_typ,  "cur_type disagrees");
      check(cur_len     === m_len,  "cur_len disagrees");
      check(consumed    === m_cons, "consumed disagrees -- the walk is not the length the model says");
      check(total_len   === m_tot,  "total_len disagrees");
      check(if_seen     === m_ifs,  "if_seen disagrees");
      check(if_expected === m_ife,  "if_expected disagrees");
      check(ep_seen     === m_eps,  "ep_seen disagrees");
      check(ep_expected === m_epe,  "ep_expected disagrees");
      check(err_code    === m_ec,   "err_code disagrees");
      check(err_pulse   === m_er,   "the error pulse disagrees");
      check(set_ok      === m_ok,   "the set_ok pulse disagrees");
      check(!(err_pulse && set_ok),
            "a descriptor set was reported consistent and broken in the same cycle");

      idx = m_st * 8 + (sd ? 4 : 0) + (bv ? 2 : 0) + (ed ? 1 : 0);
      if (idx < 40) begin
        if (seen_si[idx] == 0) begin seen_si[idx] = 1; n_si = n_si + 1; end
      end
      n_steps = n_steps + 1;

      // ---- advance the shadow walker ----
      ns = m_st; nlen = m_len; ntyp = m_typ; noff = m_off;
      ncons = m_cons; ntot = m_tot;
      nifs = m_ifs; nife = m_ife; neps = m_eps; nepe = m_epe;
      nhif = m_hif; nhcfg = m_hcfg;
      nec = E_NONE; ner = 1'b0; nok = 1'b0;

      if (eo) begin
        ns = W_IDLE;
      end else if (sd) begin
        ns = W_LEN; nlen = 8'd0; ntyp = 8'd0; noff = 8'd0;
        ncons = 16'd0; ntot = 16'd0;
        nifs = 8'd0; nife = 8'd0; neps = 8'd0; nepe = 8'd0;
        nhif = 1'b0; nhcfg = 1'b0;
      end else if (ed) begin
        if ((m_st != W_IDLE) && (m_st != W_DEAD)) begin
          if (nhif && (m_eps != m_epe)) begin
            ner = 1'b1; nec = E_EPCOUNT;
          end else if (nhcfg && (m_cons != m_tot)) begin
            ner = 1'b1; nec = E_TOTAL;
          end else if (nhcfg && (m_ifs != m_ife)) begin
            ner = 1'b1; nec = E_IFCOUNT;
          end else if (m_st == W_LEN) begin
            nok = 1'b1;
          end else begin
            ner = 1'b1; nec = E_OVERRUN;
          end
        end
        ns = W_IDLE;
      end else if (bv) begin
        if (m_st == W_LEN) begin
          nlen = bd; noff = 8'd1; ncons = m_cons + 16'd1;
          if (bd == 8'd0) begin
            ner = 1'b1; nec = E_ZEROLEN; ns = W_DEAD;
          end else ns = W_TYPE;
        end else if (m_st == W_TYPE) begin
          ntyp = bd; noff = 8'd2; ncons = m_cons + 16'd1;
          if (m_len < min_len(bd)) begin
            ner = 1'b1; nec = E_SHORT; ns = W_DEAD;
          end else if (bd == T_ENDPOINT) begin
            if (!m_hif) begin
              ner = 1'b1; nec = E_ORPHAN; ns = W_DEAD;
            end else begin
              neps = m_eps + 8'd1;
              ns = (m_len == 8'd2) ? W_LEN : W_BODY;
            end
          end else if (bd == T_INTERFACE) begin
            if (m_hif && (m_eps != m_epe)) begin
              ner = 1'b1; nec = E_EPCOUNT; ns = W_DEAD;
            end else begin
              nhif = 1'b1; nifs = m_ifs + 8'd1; neps = 8'd0;
              ns = (m_len == 8'd2) ? W_LEN : W_BODY;
            end
          end else begin
            if (bd == T_CONFIG) nhcfg = 1'b1;
            ns = (m_len == 8'd2) ? W_LEN : W_BODY;
          end
        end else if (m_st == W_BODY) begin
          ncons = m_cons + 16'd1; noff = m_off + 8'd1;
          if (m_typ == T_CONFIG) begin
            if (m_off == 8'd2) ntot = {m_tot[15:8], bd};
            if (m_off == 8'd3) ntot = {bd, m_tot[7:0]};
            if (m_off == 8'd4) nife = bd;
          end else if (m_typ == T_INTERFACE) begin
            if (m_off == 8'd4) nepe = bd;
          end
          if (m_off + 8'd1 >= m_len) ns = W_LEN;
          if (nhcfg && (m_tot != 16'd0) && (ncons > m_tot)) begin
            ner = 1'b1; nec = E_OVERRUN; ns = W_DEAD;
          end
        end else begin
          ncons = m_cons + 16'd1;
        end
      end

      if (bv && (m_st == W_TYPE) && !sd && !ed && !eo) m_d = m_d + 1;

      m_st = ns; m_len = nlen; m_typ = ntyp; m_off = noff;
      m_cons = ncons; m_tot = ntot;
      m_ifs = nifs; m_ife = nife; m_eps = neps; m_epe = nepe;
      m_hif = nhif; m_hcfg = nhcfg;
      m_ec = nec; m_er = ner; m_ok = nok;
      if (nok) m_sok = m_sok + 1;
      if (ner) begin
        case (nec)
          E_ZEROLEN: m_zl = m_zl + 1;
          E_SHORT:   m_sh = m_sh + 1;
          E_OVERRUN: m_ov = m_ov + 1;
          E_TOTAL:   m_to = m_to + 1;
          E_IFCOUNT: m_ic = m_ic + 1;
          E_EPCOUNT: m_ep = m_ep + 1;
          E_ORPHAN:  m_or = m_or + 1;
          default: ;
        endcase
      end

      @(posedge clk); #1;
      sod = 1'b0; byte_valid = 1'b0; eod = 1'b0; eot = 1'b0;
    end
  endtask

  task put(input [7:0] b);
    begin step(1'b0, 1'b1, b, 1'b0, 1'b0); end
  endtask

  task start_set;
    begin step(1'b1, 1'b0, 8'd0, 1'b0, 1'b0); end
  endtask

  task end_set;
    begin step(1'b0, 1'b0, 8'd0, 1'b1, 1'b0); end
  endtask

  task nop(input integer n);
    integer j;
    begin
      for (j = 0; j < n; j = j + 1) step(1'b0, 1'b0, 8'd0, 1'b0, 1'b0);
    end
  endtask

  // Emit a record whose bLength FIELD and actual byte COUNT can differ --
  // which is the whole point, because they differ in the bug this chapter
  // is about.
  integer ei;
  task emit(input [7:0] blen_field, input integer nbytes, input [7:0] typ,
            input [7:0] b2, input [7:0] b3, input [7:0] b4);
    begin
      for (ei = 0; ei < nbytes; ei = ei + 1) begin
        case (ei)
          0: put(blen_field);
          1: put(typ);
          2: put(b2);
          3: put(b3);
          4: put(b4);
          default: put(8'h00);
        endcase
      end
    end
  endtask

  // The canonical set: CONFIG(9) + INTERFACE(9) + ENDPOINT(7) x2 = 32
  task legal_set;
    begin
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd32, 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set;
    end
  endtask

  integer k, b_e, b_ok, L, st2, cb, total_now;

  initial begin
    model_reset;
    repeat (3) @(posedge clk);
    rst_n = 1'b1;
    @(posedge clk); #1;

    // ---- Phase A: the state after reset ----
    check(state === W_IDLE, "reset did not land idle");
    check(err_pulse === 1'b0, "reset reported a descriptor error");

    // ---- Phase B: legal sets of several shapes. ZERO errors. ----
    for (k = 0; k < 60; k = k + 1) begin
      b_ok = n_sets_ok;
      b_e  = n_zerolen + n_short + n_overrun + n_total + n_ifcount
           + n_epcount + n_orphan;
      legal_set;
      check(n_sets_ok == b_ok + 1,
            "a perfectly consistent descriptor set was not accepted");
      check(n_zerolen + n_short + n_overrun + n_total + n_ifcount
            + n_epcount + n_orphan == b_e,
            "a perfectly consistent descriptor set produced an error -- a validator with false positives is a validator somebody switches off, and then nobody is checking descriptors at all");

      // an interface with NO endpoints is perfectly legal
      b_ok = n_sets_ok;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd18, 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd0);
      end_set;
      check(n_sets_ok == b_ok + 1,
            "an interface with no endpoints was rejected, and that is a legal descriptor");

      // TWO interfaces, one endpoint each
      b_ok = n_sets_ok;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd41, 8'd0, 8'd2);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h03, 8'd0);
      emit(8'd9, 9, T_INTERFACE, 8'd1, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set;
      check(n_sets_ok == b_ok + 1, "a two-interface set was rejected");
    end

    // ---- Phase C: bLength = 0 is a HANG, and has its own code. ----
    for (k = 0; k < 20; k = k + 1) begin
      b_e = n_zerolen;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd25, 8'd0, 8'd1);
      put(8'd0);                       // a record claiming zero length
      end_set;
      check(n_zerolen == b_e + 1,
            "a zero bLength was not reported as its own class -- the walk advances by bLength, so zero advances it by nothing and a host that does not bound its walk HANGS rather than failing");
      check(state === W_IDLE, "the walker did not stop after a zero length");
    end

    // ---- Phase D: shorter than the type requires, for each type. ----
    for (L = 2; L < MIN_CFG; L = L + 1) begin
      b_e = n_short;
      start_set;
      emit(L[7:0], L, T_CONFIG, 8'd20, 8'd0, 8'd1);
      end_set;
      check(n_short == b_e + 1,
            "a CONFIGURATION shorter than its minimum was accepted -- the record cannot hold the fields its type is defined to have, so every field the host reads from it comes from the NEXT record");
    end
    for (L = 2; L < MIN_EP; L = L + 1) begin
      b_e = n_short;
      start_set;
      // wTotalLength is set to what the set ACTUALLY is (9 + 9 + L), so the
      // overrun check cannot fire first and mask the one under test. An
      // injected defect that is pre-empted by a different injected defect
      // tests neither.
      emit(8'd9, 9, T_CONFIG, (8'd18 + L[7:0]), 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd1);
      emit(L[7:0], L, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      end_set;
      check(n_short == b_e + 1, "an ENDPOINT shorter than its minimum was accepted");
    end

    // ---- Phase E: THE CHAPTER. wTotalLength disagrees with the sum. ----
    //
    // Off by ONE byte. The host reads the right number of bytes and lands
    // one byte into the next record, so every field after that point comes
    // from the wrong offset.
    for (k = 0; k < 20; k = k + 1) begin
      // declared LONGER than the set actually is
      b_e = n_total;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd33, 8'd0, 8'd1);   // says 33, is 32
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set;
      check(n_total == b_e + 1,
            "wTotalLength disagreed with the sum of the bLengths and it was not reported -- the host walks by bLength and reads by wTotalLength, and when they differ it lands in the middle of a record and reports an error against a field that is perfectly correct");

      // declared SHORTER than the set actually is: the walk runs past it
      b_e = n_overrun;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd31, 8'd0, 8'd1);   // says 31, is 32
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set;
      check(n_overrun == b_e + 1,
            "the walk ran past wTotalLength and it was not reported");
    end

    // ---- Phase F: bNumInterfaces disagrees with what is there. ----
    for (k = 0; k < 20; k = k + 1) begin
      b_e = n_ifcount;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd32, 8'd0, 8'd2);   // claims 2 interfaces
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);  // ...there is 1
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set;
      check(n_ifcount == b_e + 1,
            "bNumInterfaces disagreed with the number of interface records present and it was not reported");
    end

    // ---- Phase G: bNumEndpoints disagrees, on the last interface AND on
    // ---- one that is followed by another interface.
    for (k = 0; k < 20; k = k + 1) begin
      b_e = n_epcount;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd32, 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd3);  // claims 3
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);  // ...there are 2
      end_set;
      check(n_epcount == b_e + 1,
            "the LAST interface's endpoint count was not checked -- every other interface is checked when the next one starts, and the last one has no next");

      b_e = n_epcount;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd41, 8'd0, 8'd2);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);  // claims 2, has 1
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h03, 8'd0);
      emit(8'd9, 9, T_INTERFACE, 8'd1, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set;
      check(n_epcount == b_e + 1,
            "an interface's endpoint count was not checked when the next interface began");
    end

    // ---- Phase H: an ENDPOINT with no INTERFACE above it. ----
    for (k = 0; k < 20; k = k + 1) begin
      b_e = n_orphan;
      start_set;
      emit(8'd9, 9, T_CONFIG, 8'd16, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      end_set;
      check(n_orphan == b_e + 1,
            "an endpoint appeared before any interface and it was not reported -- it has no owner, and the host will attribute it to whatever interface comes next");
    end

    // ---- Phase I: ALL 256 bLength VALUES on the first record. ----
    //
    // The validator must reject exactly three populations: zero (a hang),
    // everything below the type's minimum (a wrong value), and nothing
    // else. Anything in between is accepted, walked, and produces whatever
    // the rest of the set implies.
    for (L = 0; L < 256; L = L + 1) begin
      b_e = n_zerolen; b_ok = n_short;
      start_set;
      // At least two bytes whenever bLength is non-zero, so the TYPE is
      // always known: SHORT is a statement about a length relative to a
      // type, and a record truncated before its type byte is an overrun
      // instead -- a different finding about a different defect.
      emit(L[7:0], (L == 0) ? 1 : ((L > 9) ? 9 : ((L < 2) ? 2 : L)),
           T_CONFIG, 8'd32, 8'd0, 8'd1);
      end_set;
      if (seen_len[L] == 0) begin seen_len[L] = 1; n_len = n_len + 1; end
      if (L == 0) begin
        check(n_zerolen == b_e + 1,
              "bLength zero was not reported as a zero length");
        check(n_short == b_ok,
              "bLength zero was classified as SHORT -- zero is a hang and short is a wrong value, and a reader needs to know which symptom to expect");
      end else if (L < MIN_CFG) begin
        check(n_short == b_ok + 1,
              "a CONFIGURATION below its minimum length was accepted");
        check(n_zerolen == b_e, "a short length was classified as zero");
      end else begin
        check(n_zerolen == b_e && n_short == b_ok,
              "a bLength at or above the type's minimum was rejected");
      end
    end

    // ---- Phase J: EXHAUSTIVE. Every walker state x every input. ----
    for (st2 = 0; st2 < 5; st2 = st2 + 1) begin
      for (cb = 0; cb < 8; cb = cb + 1) begin
        step(1'b0,1'b0,8'd0,1'b0,1'b1);      // back to idle
        case (st2)
          0: ;
          1: start_set;
          2: begin start_set; put(8'd9); end
          3: begin start_set; put(8'd9); put(T_CONFIG); end
          4: begin start_set; put(8'd0); end       // the walk is dead
        endcase
        check(state === st2[2:0],
              "the sweep could not reach the walker state it meant to reach");
        step(cb[2], cb[1], 8'h20, cb[0], 1'b0);
        step(cb[2], cb[1], 8'h20, cb[0], 1'b0);
      end
    end

    // ---- Phase K: random bytes, with set boundaries thrown in ----
    for (k = 0; k < 30000; k = k + 1)
      step(($unsigned($random) % 1000) < 22,
           ($unsigned($random) % 100) < 78,
           $random,
           ($unsigned($random) % 1000) < 25,
           1'b0);

    // ---- Phase L: and legal sets afterwards, so the validator is shown to
    // ---- still work rather than merely to have stopped.
    step(1'b0,1'b0,8'd0,1'b0,1'b1);
    b_ok = n_sets_ok;
    for (k = 0; k < 60; k = k + 1) legal_set;
    check(n_sets_ok == b_ok + 60,
          "the validator stopped accepting consistent descriptor sets");

    // ---- Final agreement ----
    check(n_desc     === m_d[31:0],   "n_desc disagrees with the model");
    check(n_zerolen  === m_zl[31:0],  "n_zerolen disagrees");
    check(n_short    === m_sh[31:0],  "n_short disagrees");
    check(n_overrun  === m_ov[31:0],  "n_overrun disagrees");
    check(n_total    === m_to[31:0],  "n_total disagrees");
    check(n_ifcount  === m_ic[31:0],  "n_ifcount disagrees");
    check(n_epcount  === m_ep[31:0],  "n_epcount disagrees");
    check(n_orphan   === m_or[31:0],  "n_orphan disagrees");
    check(n_sets_ok  === m_sok[31:0], "n_sets_ok disagrees");

    check(n_len == 256, "not every bLength value was driven on the first record");
    check(n_si  == 40,  "not every walker state was crossed with every input combination");
    check(n_zerolen > 32'd0, "a zero bLength was never seen");
    check(n_short   > 32'd0, "a short descriptor was never seen");
    check(n_overrun > 32'd0, "the walk never ran past wTotalLength");
    check(n_total   > 32'd0, "wTotalLength never disagreed with the sum");
    check(n_ifcount > 32'd0, "bNumInterfaces never disagreed");
    check(n_epcount > 32'd0, "bNumEndpoints never disagreed");
    check(n_orphan  > 32'd0, "an orphan endpoint was never seen");
    check(n_sets_ok > 32'd100, "too few consistent sets to have tested for false positives");

    $display("REACH blength=%0d/256 state-x-input=%0d/40 steps=%0d",
             n_len, n_si, n_steps);
    $display("COUNTERS desc=%0d ok=%0d zerolen=%0d short=%0d overrun=%0d total=%0d ifcount=%0d epcount=%0d orphan=%0d",
             n_desc, n_sets_ok, n_zerolen, n_short, n_overrun, n_total,
             n_ifcount, n_epcount, n_orphan);
    $display("%0s: %0d errors in %0d checks", (errors==0)?"PASS":"FAIL", errors, checks);
    $finish;
  end
endmodule

10.2 SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Testbench for usb_descriptor_validator (SystemVerilog).
//
// WHAT IS EXHAUSTIVE HERE
//
//   1. ALL 256 VALUES of bLength on the first record of a set, with the
//      type fixed at CONFIGURATION. The validator must reject exactly
//      three populations -- zero, everything below the type's minimum, and
//      nothing else -- and it must reject zero with a DIFFERENT code,
//      because zero is a hang and short is a wrong value.
//
//   2. Every walker state crossed with all eight combinations of
//      {byte_valid, sod, eod} = 40 pairs, each state reached by real bytes.
//
// AND THE SET THE CHAPTER IS ABOUT
//
// A descriptor set whose wTotalLength disagrees with the sum of its
// bLengths by ONE byte. The host reads the right number of bytes and lands
// one byte into the next record, so every field it reads after that point
// comes from the wrong offset -- and the error it eventually reports is
// against a field that is perfectly correct.
`timescale 1ns/1ps
module tb_dv_sv;
  import usb_desc_pkg::*;

  localparam int MIN_CFG = 9;
  localparam int MIN_IF  = 9;
  localparam int MIN_EP  = 7;
  localparam int MIN_ANY = 2;

  localparam [7:0] T_DEVICE=8'd1, T_CONFIG=8'd2, T_STRING=8'd3,
                   T_INTERFACE=8'd4, T_ENDPOINT=8'd5;

  logic clk = 1'b0, rst_n = 1'b0;
  logic sod = 1'b0, byte_valid = 1'b0, eod = 1'b0, eot = 1'b0;
  logic [7:0] byte_data = 8'd0;

  walk_state_e state;
  desc_err_e   err_code;
  logic [7:0]  cur_type, cur_len, if_seen, if_expected, ep_seen, ep_expected;
  logic [15:0] consumed, total_len;
  logic err_pulse, set_ok;
  logic [31:0] n_desc, n_zerolen, n_short, n_overrun, n_total,
               n_ifcount, n_epcount, n_orphan, n_sets_ok;

  usb_descriptor_validator #(.MIN_CFG(MIN_CFG), .MIN_IF(MIN_IF),
                             .MIN_EP(MIN_EP), .MIN_ANY(MIN_ANY)) dut (.*);

  always #5 clk = ~clk;

  int errors = 0, checks = 0;
  task automatic check(input logic cond, input string msg);
    begin
      checks++;
      if (!cond) begin
        errors++;
        if (errors <= 25)
          $display("FAIL @%0t: %0s | st=%0d typ=%0d len=%0d cons=%0d tot=%0d if=%0d/%0d ep=%0d/%0d e=%b(%0d)",
                   $time, msg, state, cur_type, cur_len, consumed, total_len,
                   if_seen, if_expected, ep_seen, ep_expected, err_pulse, err_code);
      end
    end
  endtask

  // ------------------------------------------------------------------
  // The shadow walker. Written from the descriptor format, not the design.
  // ------------------------------------------------------------------
  walk_state_e m_st;
  desc_err_e   m_ec;
  logic [7:0]  m_len, m_typ, m_off, m_ifs, m_ife, m_eps, m_epe;
  logic [15:0] m_cons, m_tot;
  logic        m_hif, m_hcfg, m_er, m_ok;
  int m_d, m_zl, m_sh, m_ov, m_to, m_ic, m_ep, m_or, m_sok;

  int seen_len [256];           // all 256 bLength values on record 0
  int seen_si  [40];            // 5 states x 8 input combinations
  int n_len, n_si, n_steps;

  task automatic model_reset();
    int j;
    begin
      m_st = W_IDLE; m_ec = E_NONE;
      m_len = 8'd0; m_typ = 8'd0; m_off = 8'd0;
      m_ifs = 8'd0; m_ife = 8'd0; m_eps = 8'd0; m_epe = 8'd0;
      m_cons = 16'd0; m_tot = 16'd0;
      m_hif = 1'b0; m_hcfg = 1'b0; m_er = 1'b0; m_ok = 1'b0;
      m_d = 0; m_zl = 0; m_sh = 0; m_ov = 0; m_to = 0;
      m_ic = 0; m_ep = 0; m_or = 0; m_sok = 0;
      for (j = 0; j < 256; j++) seen_len[j] = 0;
      for (j = 0; j < 40;  j = j + 1) seen_si[j]  = 0;
      n_len = 0; n_si = 0; n_steps = 0;
    end
  endtask

  function automatic logic [7:0] min_len(input logic [7:0] t);
    begin
      if (t == T_CONFIG)         min_len = 8'(MIN_CFG);
      else if (t == T_INTERFACE) min_len = 8'(MIN_IF);
      else if (t == T_ENDPOINT)  min_len = 8'(MIN_EP);
      else                       min_len = 8'(MIN_ANY);
    end
  endfunction

  int idx;
  task automatic step(input logic sd, input logic bv, input logic [7:0] bd,
                      input logic ed, input logic eo);
    walk_state_e ns;
    desc_err_e   nec;
    logic [7:0]  nlen, ntyp, noff, nifs, nife, neps, nepe;
    logic [15:0] ncons, ntot;
    logic        nhif, nhcfg, ner, nok;
    begin
      sod = sd; byte_valid = bv; byte_data = bd; eod = ed; eot = eo;
      #1;

      check(state       === m_st,   "state disagrees with the shadow walker");
      check(cur_type    === m_typ,  "cur_type disagrees");
      check(cur_len     === m_len,  "cur_len disagrees");
      check(consumed    === m_cons, "consumed disagrees -- the walk is not the length the model says");
      check(total_len   === m_tot,  "total_len disagrees");
      check(if_seen     === m_ifs,  "if_seen disagrees");
      check(if_expected === m_ife,  "if_expected disagrees");
      check(ep_seen     === m_eps,  "ep_seen disagrees");
      check(ep_expected === m_epe,  "ep_expected disagrees");
      check(err_code    === m_ec,   "err_code disagrees");
      check(err_pulse   === m_er,   "the error pulse disagrees");
      check(set_ok      === m_ok,   "the set_ok pulse disagrees");
      check(!(err_pulse && set_ok),
            "a descriptor set was reported consistent and broken in the same cycle");

      idx = int'(m_st) * 8 + (sd ? 4 : 0) + (bv ? 2 : 0) + (ed ? 1 : 0);
      if (idx < 40) begin
        if (seen_si[idx] == 0) begin seen_si[idx] = 1; n_si = n_si + 1; end
      end
      n_steps++;

      // ---- advance the shadow walker ----
      ns = m_st; nlen = m_len; ntyp = m_typ; noff = m_off;
      ncons = m_cons; ntot = m_tot;
      nifs = m_ifs; nife = m_ife; neps = m_eps; nepe = m_epe;
      nhif = m_hif; nhcfg = m_hcfg;
      nec = E_NONE; ner = 1'b0; nok = 1'b0;

      if (eo) begin
        ns = W_IDLE;
      end else if (sd) begin
        ns = W_LEN; nlen = 8'd0; ntyp = 8'd0; noff = 8'd0;
        ncons = 16'd0; ntot = 16'd0;
        nifs = 8'd0; nife = 8'd0; neps = 8'd0; nepe = 8'd0;
        nhif = 1'b0; nhcfg = 1'b0;
      end else if (ed) begin
        if ((m_st != W_IDLE) && (m_st != W_DEAD)) begin
          if (nhif && (m_eps != m_epe)) begin
            ner = 1'b1; nec = E_EPCOUNT;
          end else if (nhcfg && (m_cons != m_tot)) begin
            ner = 1'b1; nec = E_TOTAL;
          end else if (nhcfg && (m_ifs != m_ife)) begin
            ner = 1'b1; nec = E_IFCOUNT;
          end else if (m_st == W_LEN) begin
            nok = 1'b1;
          end else begin
            ner = 1'b1; nec = E_OVERRUN;
          end
        end
        ns = W_IDLE;
      end else if (bv) begin
        if (m_st == W_LEN) begin
          nlen = bd; noff = 8'd1; ncons = m_cons + 16'd1;
          if (bd == 8'd0) begin
            ner = 1'b1; nec = E_ZEROLEN; ns = W_DEAD;
          end else ns = W_TYPE;
        end else if (m_st == W_TYPE) begin
          ntyp = bd; noff = 8'd2; ncons = m_cons + 16'd1;
          if (m_len < min_len(bd)) begin
            ner = 1'b1; nec = E_SHORT; ns = W_DEAD;
          end else if (bd == T_ENDPOINT) begin
            if (!m_hif) begin
              ner = 1'b1; nec = E_ORPHAN; ns = W_DEAD;
            end else begin
              neps = m_eps + 8'd1;
              if (m_len == 8'd2) ns = W_LEN; else ns = W_BODY;
            end
          end else if (bd == T_INTERFACE) begin
            if (m_hif && (m_eps != m_epe)) begin
              ner = 1'b1; nec = E_EPCOUNT; ns = W_DEAD;
            end else begin
              nhif = 1'b1; nifs = m_ifs + 8'd1; neps = 8'd0;
              if (m_len == 8'd2) ns = W_LEN; else ns = W_BODY;
            end
          end else begin
            if (bd == T_CONFIG) nhcfg = 1'b1;
            if (m_len == 8'd2) ns = W_LEN; else ns = W_BODY;
          end
        end else if (m_st == W_BODY) begin
          ncons = m_cons + 16'd1; noff = m_off + 8'd1;
          if (m_typ == T_CONFIG) begin
            if (m_off == 8'd2) ntot = {m_tot[15:8], bd};
            if (m_off == 8'd3) ntot = {bd, m_tot[7:0]};
            if (m_off == 8'd4) nife = bd;
          end else if (m_typ == T_INTERFACE) begin
            if (m_off == 8'd4) nepe = bd;
          end
          if (m_off + 8'd1 >= m_len) ns = W_LEN;
          if (nhcfg && (m_tot != 16'd0) && (ncons > m_tot)) begin
            ner = 1'b1; nec = E_OVERRUN; ns = W_DEAD;
          end
        end else begin
          ncons = m_cons + 16'd1;
        end
      end

      if (bv && (m_st == W_TYPE) && !sd && !ed && !eo) m_d = m_d + 1;

      m_st = ns; m_len = nlen; m_typ = ntyp; m_off = noff;
      m_cons = ncons; m_tot = ntot;
      m_ifs = nifs; m_ife = nife; m_eps = neps; m_epe = nepe;
      m_hif = nhif; m_hcfg = nhcfg;
      m_ec = nec; m_er = ner; m_ok = nok;
      if (nok) m_sok = m_sok + 1;
      if (ner) begin
        case (nec)
          E_ZEROLEN: m_zl = m_zl + 1;
          E_SHORT:   m_sh = m_sh + 1;
          E_OVERRUN: m_ov = m_ov + 1;
          E_TOTAL:   m_to = m_to + 1;
          E_IFCOUNT: m_ic = m_ic + 1;
          E_EPCOUNT: m_ep = m_ep + 1;
          E_ORPHAN:  m_or = m_or + 1;
          default: ;
        endcase
      end

      @(posedge clk); #1;
      sod = 1'b0; byte_valid = 1'b0; eod = 1'b0; eot = 1'b0;
    end
  endtask

  task automatic put(input logic [7:0] b);
    step(1'b0, 1'b1, b, 1'b0, 1'b0);
  endtask

  task automatic start_set();
    step(1'b1, 1'b0, 8'd0, 1'b0, 1'b0);
  endtask

  task automatic end_set();
    step(1'b0, 1'b0, 8'd0, 1'b1, 1'b0);
  endtask

  task automatic nop(input int n);
    repeat (n) step(1'b0, 1'b0, 8'd0, 1'b0, 1'b0);
  endtask

  // Emit a record whose bLength FIELD and actual byte COUNT can differ --
  // which is the whole point, because they differ in the bug this chapter
  // is about.
  int ei;
  task automatic emit(input logic [7:0] blen_field, input int nbytes,
                      input logic [7:0] typ, input logic [7:0] b2,
                      input logic [7:0] b3, input logic [7:0] b4);
    begin
      for (ei = 0; ei < nbytes; ei++) begin
        case (ei)
          0: put(blen_field);
          1: put(typ);
          2: put(b2);
          3: put(b3);
          4: put(b4);
          default: put(8'h00);
        endcase
      end
    end
  endtask

  // The canonical set: CONFIG(9) + INTERFACE(9) + ENDPOINT(7) x2 = 32
  task automatic legal_set();
    begin
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd32, 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set();
    end
  endtask

  int k, b_e, b_ok, L, st2, cb, total_now;

  initial begin
    model_reset();
    repeat (3) @(posedge clk);
    rst_n = 1'b1;
    @(posedge clk); #1;

    // ---- Phase A: the state after reset ----
    check(state === W_IDLE, "reset did not land idle");
    check(err_pulse === 1'b0, "reset reported a descriptor error");

    // ---- Phase B: legal sets of several shapes. ZERO errors. ----
    for (k = 0; k < 60; k++) begin
      b_ok = n_sets_ok;
      b_e  = n_zerolen + n_short + n_overrun + n_total + n_ifcount
           + n_epcount + n_orphan;
      legal_set();
      check(n_sets_ok == b_ok + 1,
            "a perfectly consistent descriptor set was not accepted");
      check(n_zerolen + n_short + n_overrun + n_total + n_ifcount
            + n_epcount + n_orphan == b_e,
            "a perfectly consistent descriptor set produced an error -- a validator with false positives is a validator somebody switches off, and then nobody is checking descriptors at all");

      // an interface with NO endpoints is perfectly legal
      b_ok = n_sets_ok;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd18, 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd0);
      end_set();
      check(n_sets_ok == b_ok + 1,
            "an interface with no endpoints was rejected, and that is a legal descriptor");

      // TWO interfaces, one endpoint each
      b_ok = n_sets_ok;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd41, 8'd0, 8'd2);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h03, 8'd0);
      emit(8'd9, 9, T_INTERFACE, 8'd1, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set();
      check(n_sets_ok == b_ok + 1, "a two-interface set was rejected");
    end

    // ---- Phase C: bLength = 0 is a HANG, and has its own code. ----
    for (k = 0; k < 20; k++) begin
      b_e = n_zerolen;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd25, 8'd0, 8'd1);
      put(8'd0);                       // a record claiming zero length
      end_set();
      check(n_zerolen == b_e + 1,
            "a zero bLength was not reported as its own class -- the walk advances by bLength, so zero advances it by nothing and a host that does not bound its walk HANGS rather than failing");
      check(state === W_IDLE, "the walker did not stop after a zero length");
    end

    // ---- Phase D: shorter than the type requires, for each type. ----
    for (L = 2; L < MIN_CFG; L++) begin
      b_e = n_short;
      start_set();
      emit(8'(L), L, T_CONFIG, 8'd20, 8'd0, 8'd1);
      end_set();
      check(n_short == b_e + 1,
            "a CONFIGURATION shorter than its minimum was accepted -- the record cannot hold the fields its type is defined to have, so every field the host reads from it comes from the NEXT record");
    end
    for (L = 2; L < MIN_EP; L++) begin
      b_e = n_short;
      start_set();
      // wTotalLength is set to what the set ACTUALLY is (9 + 9 + L), so the
      // overrun check cannot fire first and mask the one under test. An
      // injected defect that is pre-empted by a different injected defect
      // tests neither.
      emit(8'd9, 9, T_CONFIG, (8'd18 + 8'(L)), 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd1);
      emit(8'(L), L, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      end_set();
      check(n_short == b_e + 1, "an ENDPOINT shorter than its minimum was accepted");
    end

    // ---- Phase E: THE CHAPTER. wTotalLength disagrees with the sum. ----
    //
    // Off by ONE byte. The host reads the right number of bytes and lands
    // one byte into the next record, so every field after that point comes
    // from the wrong offset.
    for (k = 0; k < 20; k++) begin
      // declared LONGER than the set actually is
      b_e = n_total;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd33, 8'd0, 8'd1);   // says 33, is 32
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set();
      check(n_total == b_e + 1,
            "wTotalLength disagreed with the sum of the bLengths and it was not reported -- the host walks by bLength and reads by wTotalLength, and when they differ it lands in the middle of a record and reports an error against a field that is perfectly correct");

      // declared SHORTER than the set actually is: the walk runs past it
      b_e = n_overrun;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd31, 8'd0, 8'd1);   // says 31, is 32
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set();
      check(n_overrun == b_e + 1,
            "the walk ran past wTotalLength and it was not reported");
    end

    // ---- Phase F: bNumInterfaces disagrees with what is there. ----
    for (k = 0; k < 20; k++) begin
      b_e = n_ifcount;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd32, 8'd0, 8'd2);   // claims 2 interfaces
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);  // ...there is 1
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set();
      check(n_ifcount == b_e + 1,
            "bNumInterfaces disagreed with the number of interface records present and it was not reported");
    end

    // ---- Phase G: bNumEndpoints disagrees, on the last interface AND on
    // ---- one that is followed by another interface.
    for (k = 0; k < 20; k++) begin
      b_e = n_epcount;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd32, 8'd0, 8'd1);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd3);  // claims 3
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);  // ...there are 2
      end_set();
      check(n_epcount == b_e + 1,
            "the LAST interface's endpoint count was not checked -- every other interface is checked when the next one starts, and the last one has no next");

      b_e = n_epcount;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd41, 8'd0, 8'd2);
      emit(8'd9, 9, T_INTERFACE, 8'd0, 8'd0, 8'd2);  // claims 2, has 1
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h03, 8'd0);
      emit(8'd9, 9, T_INTERFACE, 8'd1, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h02, 8'h02, 8'd0);
      end_set();
      check(n_epcount == b_e + 1,
            "an interface's endpoint count was not checked when the next interface began");
    end

    // ---- Phase H: an ENDPOINT with no INTERFACE above it. ----
    for (k = 0; k < 20; k++) begin
      b_e = n_orphan;
      start_set();
      emit(8'd9, 9, T_CONFIG, 8'd16, 8'd0, 8'd1);
      emit(8'd7, 7, T_ENDPOINT, 8'h81, 8'h02, 8'd0);
      end_set();
      check(n_orphan == b_e + 1,
            "an endpoint appeared before any interface and it was not reported -- it has no owner, and the host will attribute it to whatever interface comes next");
    end

    // ---- Phase I: ALL 256 bLength VALUES on the first record. ----
    //
    // The validator must reject exactly three populations: zero (a hang),
    // everything below the type's minimum (a wrong value), and nothing
    // else. Anything in between is accepted, walked, and produces whatever
    // the rest of the set implies.
    for (L = 0; L < 256; L++) begin
      b_e = n_zerolen; b_ok = n_short;
      start_set();
      // At least two bytes whenever bLength is non-zero, so the TYPE is
      // always known: SHORT is a statement about a length relative to a
      // type, and a record truncated before its type byte is an overrun
      // instead -- a different finding about a different defect.
      emit(8'(L), (L == 0) ? 1 : ((L > 9) ? 9 : ((L < 2) ? 2 : L)),
           T_CONFIG, 8'd32, 8'd0, 8'd1);
      end_set();
      if (seen_len[L] == 0) begin seen_len[L] = 1; n_len = n_len + 1; end
      if (L == 0) begin
        check(n_zerolen == b_e + 1,
              "bLength zero was not reported as a zero length");
        check(n_short == b_ok,
              "bLength zero was classified as SHORT -- zero is a hang and short is a wrong value, and a reader needs to know which symptom to expect");
      end else if (L < MIN_CFG) begin
        check(n_short == b_ok + 1,
              "a CONFIGURATION below its minimum length was accepted");
        check(n_zerolen == b_e, "a short length was classified as zero");
      end else begin
        check(n_zerolen == b_e && n_short == b_ok,
              "a bLength at or above the type's minimum was rejected");
      end
    end

    // ---- Phase J: EXHAUSTIVE. Every walker state x every input. ----
    for (st2 = 0; st2 < 5; st2++) begin
      for (cb = 0; cb < 8; cb++) begin
        step(1'b0,1'b0,8'd0,1'b0,1'b1);      // back to idle
        case (st2)
          0: ;
          1: start_set();
          2: begin start_set(); put(8'd9); end
          3: begin start_set(); put(8'd9); put(T_CONFIG); end
          4: begin start_set(); put(8'd0); end       // the walk is dead
        endcase
        check(state === walk_state_e'(st2),
              "the sweep could not reach the walker state it meant to reach");
        step(1'(cb[2]), 1'(cb[1]), 8'h20, 1'(cb[0]), 1'b0);
        step(1'(cb[2]), 1'(cb[1]), 8'h20, 1'(cb[0]), 1'b0);
      end
    end

    // ---- Phase K: random bytes, with set boundaries thrown in ----
    for (k = 0; k < 30000; k++)
      step($urandom_range(0,999) < 22,
           $urandom_range(0,99) < 78,
           8'($urandom()),
           $urandom_range(0,999) < 25,
           1'b0);

    // ---- Phase L: and legal sets afterwards, so the validator is shown to
    // ---- still work rather than merely to have stopped.
    step(1'b0,1'b0,8'd0,1'b0,1'b1);
    b_ok = n_sets_ok;
    for (k = 0; k < 60; k++) legal_set();
    check(n_sets_ok == b_ok + 60,
          "the validator stopped accepting consistent descriptor sets");

    // ---- Final agreement ----
    check(n_desc     === 32'(m_d),   "n_desc disagrees with the model");
    check(n_zerolen  === 32'(m_zl),  "n_zerolen disagrees");
    check(n_short    === 32'(m_sh),  "n_short disagrees");
    check(n_overrun  === 32'(m_ov),  "n_overrun disagrees");
    check(n_total    === 32'(m_to),  "n_total disagrees");
    check(n_ifcount  === 32'(m_ic),  "n_ifcount disagrees");
    check(n_epcount  === 32'(m_ep),  "n_epcount disagrees");
    check(n_orphan   === 32'(m_or),  "n_orphan disagrees");
    check(n_sets_ok  === 32'(m_sok), "n_sets_ok disagrees");

    check(n_len == 256, "not every bLength value was driven on the first record");
    check(n_si  == 40,  "not every walker state was crossed with every input combination");
    check(n_zerolen > 32'd0, "a zero bLength was never seen");
    check(n_short   > 32'd0, "a short descriptor was never seen");
    check(n_overrun > 32'd0, "the walk never ran past wTotalLength");
    check(n_total   > 32'd0, "wTotalLength never disagreed with the sum");
    check(n_ifcount > 32'd0, "bNumInterfaces never disagreed");
    check(n_epcount > 32'd0, "bNumEndpoints never disagreed");
    check(n_orphan  > 32'd0, "an orphan endpoint was never seen");
    check(n_sets_ok > 32'd100, "too few consistent sets to have tested for false positives");

    $display("REACH blength=%0d/256 state-x-input=%0d/40 steps=%0d",
             n_len, n_si, n_steps);
    $display("COUNTERS desc=%0d ok=%0d zerolen=%0d short=%0d overrun=%0d total=%0d ifcount=%0d epcount=%0d orphan=%0d",
             n_desc, n_sets_ok, n_zerolen, n_short, n_overrun, n_total,
             n_ifcount, n_epcount, n_orphan);
    $display("%0s: %0d errors in %0d checks", (errors==0)?"PASS":"FAIL", errors, checks);
    $finish;
  end
endmodule

10.3 VHDL testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Testbench for usb_descriptor_validator (VHDL-2008).
--
-- WHAT IS EXHAUSTIVE HERE
--
--   1. ALL 256 VALUES of bLength on the first record of a set, with the
--      type fixed at CONFIGURATION. The validator must reject exactly
--      three populations -- zero, everything below the type's minimum, and
--      nothing else -- and it must reject zero with a DIFFERENT code,
--      because zero is a hang and short is a wrong value.
--
--   2. Every walker state crossed with all eight combinations of
--      (byte_valid, sod, eod) = 40 pairs, each state reached by real bytes.
--
-- AND THE SET THE CHAPTER IS ABOUT
--
-- A descriptor set whose wTotalLength disagrees with the sum of its
-- bLengths by ONE byte. The host reads the right number of bytes and lands
-- one byte into the next record, so every field it reads after that point
-- comes from the wrong offset -- and the error it eventually reports is
-- against a field that is perfectly correct.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.usb_desc_pkg.all;

entity tb_dv_vhdl is
end entity tb_dv_vhdl;

architecture sim of tb_dv_vhdl is

  constant MIN_CFG : integer := 9;
  constant MIN_IF  : integer := 9;
  constant MIN_EP  : integer := 7;
  constant MIN_ANY : integer := 2;

  constant T_CONFIG    : std_logic_vector(7 downto 0) := x"02";
  constant T_INTERFACE : std_logic_vector(7 downto 0) := x"04";
  constant T_ENDPOINT  : std_logic_vector(7 downto 0) := x"05";

  signal clk   : std_logic := '0';
  signal rst_n : std_logic := '0';
  signal done  : boolean   := false;

  signal sod, byte_valid, eod, eot : std_logic := '0';
  signal byte_data : std_logic_vector(7 downto 0) := (others => '0');

  signal state, err_code : std_logic_vector(2 downto 0);
  signal cur_type, cur_len : std_logic_vector(7 downto 0);
  signal if_seen, if_expected, ep_seen, ep_expected : std_logic_vector(7 downto 0);
  signal consumed, total_len : std_logic_vector(15 downto 0);
  signal err_pulse, set_ok : std_logic;
  signal n_desc, n_zerolen, n_short, n_overrun : std_logic_vector(31 downto 0);
  signal n_total, n_ifcount, n_epcount, n_orphan, n_sets_ok : std_logic_vector(31 downto 0);

begin

  dut : entity work.usb_descriptor_validator
    generic map (MIN_CFG => MIN_CFG, MIN_IF => MIN_IF,
                 MIN_EP => MIN_EP, MIN_ANY => MIN_ANY)
    port map (
      clk => clk, rst_n => rst_n,
      sod => sod, byte_valid => byte_valid, byte_data => byte_data,
      eod => eod, eot => eot,
      state => state, cur_type => cur_type, cur_len => cur_len,
      consumed => consumed, total_len => total_len,
      if_seen => if_seen, if_expected => if_expected,
      ep_seen => ep_seen, ep_expected => ep_expected,
      err_pulse => err_pulse, err_code => err_code, set_ok => set_ok,
      n_desc => n_desc, n_zerolen => n_zerolen, n_short => n_short,
      n_overrun => n_overrun, n_total => n_total, n_ifcount => n_ifcount,
      n_epcount => n_epcount, n_orphan => n_orphan, n_sets_ok => n_sets_ok
    );

  clk <= (not clk) after 5 ns when not done else '0';

  stim : process
    type len_arr is array (0 to 255) of integer;
    type si_arr  is array (0 to 39)  of integer;

    variable errors, checks : integer := 0;

    -- ---- The shadow walker. Written from the descriptor format. ----
    variable m_st  : walk_state_t := W_IDLE;
    variable m_ec  : desc_err_t   := E_NONE;
    variable m_len, m_typ, m_off : unsigned(7 downto 0) := (others => '0');
    variable m_ifs, m_ife, m_eps, m_epe : unsigned(7 downto 0) := (others => '0');
    variable m_cons, m_tot : unsigned(15 downto 0) := (others => '0');
    variable m_hif, m_hcfg, m_er, m_ok : std_logic := '0';
    variable m_d, m_zl, m_sh, m_ov, m_to : integer := 0;
    variable m_ic, m_ep, m_or, m_sok : integer := 0;

    variable seen_len : len_arr := (others => 0);
    variable seen_si  : si_arr  := (others => 0);
    variable n_len, n_si, n_steps : integer := 0;

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

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

    -- Only the low 30 bits are converted: a full 32-bit unsigned does not
    -- fit in VHDL's INTEGER, and to_integer aborts the run rather than
    -- wrapping.
    impure function rnd_nat return integer is
      variable u : unsigned(31 downto 0);
    begin
      u := rnd32;
      return to_integer(u(29 downto 0));
    end function;

    impure function rnd_byte return std_logic_vector is
      variable u : unsigned(31 downto 0);
    begin
      u := rnd32;
      return std_logic_vector(u(7 downto 0));
    end function;

    impure function rnd_lt (pct, base : integer) return std_logic is
    begin
      if (rnd_nat mod base) < pct then return '1'; else return '0'; end if;
    end function;

    function min_len_m (t : std_logic_vector(7 downto 0)) return unsigned is
    begin
      if t = T_CONFIG then return to_unsigned(MIN_CFG, 8);
      elsif t = T_INTERFACE then return to_unsigned(MIN_IF, 8);
      elsif t = T_ENDPOINT then return to_unsigned(MIN_EP, 8);
      else return to_unsigned(MIN_ANY, 8);
      end if;
    end function;

    procedure chk (cond : boolean; msg : string) is
    begin
      checks := checks + 1;
      if not cond then
        errors := errors + 1;
        if errors <= 25 then
          report "FAIL: " & msg &
                 " | st=" & integer'image(walk_state_t'pos(m_st)) &
                 " cons=" & integer'image(to_integer(m_cons)) &
                 " tot=" & integer'image(to_integer(m_tot)) &
                 " ec=" & integer'image(desc_err_t'pos(m_ec))
            severity note;
        end if;
      end if;
    end procedure;

    procedure step (sd, bv : std_logic; bd_v : std_logic_vector(7 downto 0);
                    ed, eo : std_logic) is
      variable nst  : walk_state_t;
      variable nec : desc_err_t;
      variable nlen, ntyp, noff : unsigned(7 downto 0);
      variable ncons, ntot : unsigned(15 downto 0);
      variable nifs, nife, neps, nepe : unsigned(7 downto 0);
      variable nhif, nhcfg, ner, nok : std_logic;
      variable bd : unsigned(7 downto 0);
      variable idx : integer;
    begin
      sod <= sd; byte_valid <= bv; byte_data <= bd_v; eod <= ed; eot <= eo;
      wait for 1 ns;

      chk(state = w_code(m_st), "state disagrees with the shadow walker");
      chk(unsigned(cur_type) = m_typ, "cur_type disagrees");
      chk(unsigned(cur_len)  = m_len, "cur_len disagrees");
      chk(unsigned(consumed) = m_cons,
          "consumed disagrees -- the walk is not the length the model says");
      chk(unsigned(total_len)   = m_tot, "total_len disagrees");
      chk(unsigned(if_seen)     = m_ifs, "if_seen disagrees");
      chk(unsigned(if_expected) = m_ife, "if_expected disagrees");
      chk(unsigned(ep_seen)     = m_eps, "ep_seen disagrees");
      chk(unsigned(ep_expected) = m_epe, "ep_expected disagrees");
      chk(err_code  = e_code(m_ec), "err_code disagrees");
      chk(err_pulse = m_er, "the error pulse disagrees");
      chk(set_ok    = m_ok, "the set_ok pulse disagrees");
      chk(not (err_pulse = '1' and set_ok = '1'),
          "a descriptor set was reported consistent and broken in the same cycle");

      idx := walk_state_t'pos(m_st) * 8;
      if sd = '1' then idx := idx + 4; end if;
      if bv = '1' then idx := idx + 2; end if;
      if ed = '1' then idx := idx + 1; end if;
      if idx < 40 then
        if seen_si(idx) = 0 then seen_si(idx) := 1; n_si := n_si + 1; end if;
      end if;
      n_steps := n_steps + 1;

      -- ---- advance the shadow walker ----
      bd := unsigned(bd_v);
      nst := m_st; nlen := m_len; ntyp := m_typ; noff := m_off;
      ncons := m_cons; ntot := m_tot;
      nifs := m_ifs; nife := m_ife; neps := m_eps; nepe := m_epe;
      nhif := m_hif; nhcfg := m_hcfg;
      nec := E_NONE; ner := '0'; nok := '0';

      if eo = '1' then
        nst := W_IDLE;
      elsif sd = '1' then
        nst := W_LEN;
        nlen := (others => '0'); ntyp := (others => '0');
        noff := (others => '0');
        ncons := (others => '0'); ntot := (others => '0');
        nifs := (others => '0'); nife := (others => '0');
        neps := (others => '0'); nepe := (others => '0');
        nhif := '0'; nhcfg := '0';
      elsif ed = '1' then
        if m_st /= W_IDLE and m_st /= W_DEAD then
          if nhif = '1' and m_eps /= m_epe then
            ner := '1'; nec := E_EPCOUNT;
          elsif nhcfg = '1' and m_cons /= m_tot then
            ner := '1'; nec := E_TOTAL;
          elsif nhcfg = '1' and m_ifs /= m_ife then
            ner := '1'; nec := E_IFCOUNT;
          elsif m_st = W_LEN then
            nok := '1';
          else
            ner := '1'; nec := E_OVERRUN;
          end if;
        end if;
        nst := W_IDLE;
      elsif bv = '1' then
        case m_st is
          when W_LEN =>
            nlen := bd; noff := to_unsigned(1, 8); ncons := m_cons + 1;
            if bd = 0 then
              ner := '1'; nec := E_ZEROLEN; nst := W_DEAD;
            else
              nst := W_TYPE;
            end if;
          when W_TYPE =>
            ntyp := bd; noff := to_unsigned(2, 8); ncons := m_cons + 1;
            if m_len < min_len_m(bd_v) then
              ner := '1'; nec := E_SHORT; nst := W_DEAD;
            elsif bd_v = T_ENDPOINT then
              if m_hif = '0' then
                ner := '1'; nec := E_ORPHAN; nst := W_DEAD;
              else
                neps := m_eps + 1;
                if m_len = 2 then nst := W_LEN; else nst := W_BODY; end if;
              end if;
            elsif bd_v = T_INTERFACE then
              if m_hif = '1' and m_eps /= m_epe then
                ner := '1'; nec := E_EPCOUNT; nst := W_DEAD;
              else
                nhif := '1'; nifs := m_ifs + 1; neps := (others => '0');
                if m_len = 2 then nst := W_LEN; else nst := W_BODY; end if;
              end if;
            else
              if bd_v = T_CONFIG then nhcfg := '1'; end if;
              if m_len = 2 then nst := W_LEN; else nst := W_BODY; end if;
            end if;
          when W_BODY =>
            ncons := m_cons + 1; noff := m_off + 1;
            if m_typ = unsigned(T_CONFIG) then
              if m_off = 2 then ntot := m_tot(15 downto 8) & bd; end if;
              if m_off = 3 then ntot := bd & m_tot(7 downto 0); end if;
              if m_off = 4 then nife := bd; end if;
            elsif m_typ = unsigned(T_INTERFACE) then
              if m_off = 4 then nepe := bd; end if;
            end if;
            if m_off + 1 >= m_len then nst := W_LEN; end if;
            if nhcfg = '1' and m_tot /= 0 and ncons > m_tot then
              ner := '1'; nec := E_OVERRUN; nst := W_DEAD;
            end if;
          when others =>
            ncons := m_cons + 1;
        end case;
      end if;

      if bv = '1' and m_st = W_TYPE and sd = '0' and ed = '0' and eo = '0' then
        m_d := m_d + 1;
      end if;

      m_st := nst; m_len := nlen; m_typ := ntyp; m_off := noff;
      m_cons := ncons; m_tot := ntot;
      m_ifs := nifs; m_ife := nife; m_eps := neps; m_epe := nepe;
      m_hif := nhif; m_hcfg := nhcfg;
      m_ec := nec; m_er := ner; m_ok := nok;
      if nok = '1' then m_sok := m_sok + 1; end if;
      if ner = '1' then
        case nec is
          when E_ZEROLEN => m_zl := m_zl + 1;
          when E_SHORT   => m_sh := m_sh + 1;
          when E_OVERRUN => m_ov := m_ov + 1;
          when E_TOTAL   => m_to := m_to + 1;
          when E_IFCOUNT => m_ic := m_ic + 1;
          when E_EPCOUNT => m_ep := m_ep + 1;
          when E_ORPHAN  => m_or := m_or + 1;
          when others    => null;
        end case;
      end if;

      wait until rising_edge(clk);
      wait for 1 ns;
      sod <= '0'; byte_valid <= '0'; eod <= '0'; eot <= '0';
    end procedure;

    constant Z8 : std_logic_vector(7 downto 0) := (others => '0');

    procedure put (b : std_logic_vector(7 downto 0)) is
    begin step('0', '1', b, '0', '0'); end procedure;

    procedure start_set is
    begin step('1', '0', Z8, '0', '0'); end procedure;

    procedure end_set is
    begin step('0', '0', Z8, '1', '0'); end procedure;

    function b8 (n : integer) return std_logic_vector is
    begin
      return std_logic_vector(to_unsigned(n mod 256, 8));
    end function;

    -- Emit a record whose bLength FIELD and actual byte COUNT can differ --
    -- which is the whole point, because they differ in the bug this chapter
    -- is about.
    procedure emit (blen_field : integer; nbytes : integer;
                    typ, b2, b3, b4 : std_logic_vector(7 downto 0)) is
    begin
      for i in 0 to nbytes - 1 loop
        case i is
          when 0 => put(b8(blen_field));
          when 1 => put(typ);
          when 2 => put(b2);
          when 3 => put(b3);
          when 4 => put(b4);
          when others => put(Z8);
        end case;
      end loop;
    end procedure;

    -- The canonical set: CONFIG(9) + INTERFACE(9) + ENDPOINT(7) x2 = 32
    procedure legal_set is
    begin
      start_set;
      emit(9, 9, T_CONFIG, b8(32), Z8, b8(1));
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(2));
      emit(7, 7, T_ENDPOINT, x"81", x"02", Z8);
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);
      end_set;
    end procedure;

    variable b_e, b_ok : integer := 0;
    variable sd_v, bv_v, ed_v : std_logic;
    variable ln : line;

  begin
    wait for 33 ns;
    rst_n <= '1';
    wait until rising_edge(clk);
    wait for 1 ns;

    -- ---- Phase A: the state after reset ----
    chk(state = w_code(W_IDLE), "reset did not land idle");
    chk(err_pulse = '0', "reset reported a descriptor error");

    -- ---- Phase B: legal sets of several shapes. ZERO errors. ----
    for k in 0 to 59 loop
      b_ok := to_integer(unsigned(n_sets_ok));
      b_e  := to_integer(unsigned(n_zerolen)) + to_integer(unsigned(n_short))
            + to_integer(unsigned(n_overrun)) + to_integer(unsigned(n_total))
            + to_integer(unsigned(n_ifcount)) + to_integer(unsigned(n_epcount))
            + to_integer(unsigned(n_orphan));
      legal_set;
      chk(to_integer(unsigned(n_sets_ok)) = b_ok + 1,
          "a perfectly consistent descriptor set was not accepted");
      chk(to_integer(unsigned(n_zerolen)) + to_integer(unsigned(n_short))
          + to_integer(unsigned(n_overrun)) + to_integer(unsigned(n_total))
          + to_integer(unsigned(n_ifcount)) + to_integer(unsigned(n_epcount))
          + to_integer(unsigned(n_orphan)) = b_e,
          "a perfectly consistent descriptor set produced an error -- a validator with false positives is a validator somebody switches off, and then nobody is checking descriptors at all");

      b_ok := to_integer(unsigned(n_sets_ok));
      start_set;
      emit(9, 9, T_CONFIG, b8(18), Z8, b8(1));
      emit(9, 9, T_INTERFACE, Z8, Z8, Z8);
      end_set;
      chk(to_integer(unsigned(n_sets_ok)) = b_ok + 1,
          "an interface with no endpoints was rejected, and that is a legal descriptor");

      b_ok := to_integer(unsigned(n_sets_ok));
      start_set;
      emit(9, 9, T_CONFIG, b8(41), Z8, b8(2));
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(1));
      emit(7, 7, T_ENDPOINT, x"81", x"03", Z8);
      emit(9, 9, T_INTERFACE, b8(1), Z8, b8(1));
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_sets_ok)) = b_ok + 1,
          "a two-interface set was rejected");
    end loop;

    -- ---- Phase C: bLength = 0 is a HANG, and has its own code. ----
    for k in 1 to 20 loop
      b_e := to_integer(unsigned(n_zerolen));
      start_set;
      emit(9, 9, T_CONFIG, b8(25), Z8, b8(1));
      put(Z8);
      end_set;
      chk(to_integer(unsigned(n_zerolen)) = b_e + 1,
          "a zero bLength was not reported as its own class -- the walk advances by bLength, so zero advances it by nothing and a host that does not bound its walk HANGS rather than failing");
      chk(state = w_code(W_IDLE), "the walker did not stop after a zero length");
    end loop;

    -- ---- Phase D: shorter than the type requires, for each type. ----
    for L in 2 to MIN_CFG - 1 loop
      b_e := to_integer(unsigned(n_short));
      start_set;
      emit(L, L, T_CONFIG, b8(20), Z8, b8(1));
      end_set;
      chk(to_integer(unsigned(n_short)) = b_e + 1,
          "a CONFIGURATION shorter than its minimum was accepted -- the record cannot hold the fields its type is defined to have, so every field the host reads from it comes from the NEXT record");
    end loop;
    for L in 2 to MIN_EP - 1 loop
      b_e := to_integer(unsigned(n_short));
      start_set;
      -- wTotalLength is set to what the set ACTUALLY is (9 + 9 + L), so the
      -- overrun check cannot fire first and mask the one under test.
      emit(9, 9, T_CONFIG, b8(18 + L), Z8, b8(1));
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(1));
      emit(L, L, T_ENDPOINT, x"81", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_short)) = b_e + 1,
          "an ENDPOINT shorter than its minimum was accepted");
    end loop;

    -- ---- Phase E: THE CHAPTER. wTotalLength disagrees with the sum. ----
    for k in 1 to 20 loop
      b_e := to_integer(unsigned(n_total));
      start_set;
      emit(9, 9, T_CONFIG, b8(33), Z8, b8(1));   -- says 33, is 32
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(2));
      emit(7, 7, T_ENDPOINT, x"81", x"02", Z8);
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_total)) = b_e + 1,
          "wTotalLength disagreed with the sum of the bLengths and it was not reported -- the host walks by bLength and reads by wTotalLength, and when they differ it lands in the middle of a record and reports an error against a field that is perfectly correct");

      b_e := to_integer(unsigned(n_overrun));
      start_set;
      emit(9, 9, T_CONFIG, b8(31), Z8, b8(1));   -- says 31, is 32
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(2));
      emit(7, 7, T_ENDPOINT, x"81", x"02", Z8);
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_overrun)) = b_e + 1,
          "the walk ran past wTotalLength and it was not reported");
    end loop;

    -- ---- Phase F: bNumInterfaces disagrees with what is there. ----
    for k in 1 to 20 loop
      b_e := to_integer(unsigned(n_ifcount));
      start_set;
      emit(9, 9, T_CONFIG, b8(32), Z8, b8(2));   -- claims 2 interfaces
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(2));    -- ...there is 1
      emit(7, 7, T_ENDPOINT, x"81", x"02", Z8);
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_ifcount)) = b_e + 1,
          "bNumInterfaces disagreed with the number of interface records present and it was not reported");
    end loop;

    -- ---- Phase G: bNumEndpoints disagrees. ----
    for k in 1 to 20 loop
      b_e := to_integer(unsigned(n_epcount));
      start_set;
      emit(9, 9, T_CONFIG, b8(32), Z8, b8(1));
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(3));    -- claims 3
      emit(7, 7, T_ENDPOINT, x"81", x"02", Z8);
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);  -- ...there are 2
      end_set;
      chk(to_integer(unsigned(n_epcount)) = b_e + 1,
          "the LAST interface's endpoint count was not checked -- every other interface is checked when the next one starts, and the last one has no next");

      b_e := to_integer(unsigned(n_epcount));
      start_set;
      emit(9, 9, T_CONFIG, b8(41), Z8, b8(2));
      emit(9, 9, T_INTERFACE, Z8, Z8, b8(2));    -- claims 2, has 1
      emit(7, 7, T_ENDPOINT, x"81", x"03", Z8);
      emit(9, 9, T_INTERFACE, b8(1), Z8, b8(1));
      emit(7, 7, T_ENDPOINT, x"02", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_epcount)) = b_e + 1,
          "an interface's endpoint count was not checked when the next interface began");
    end loop;

    -- ---- Phase H: an ENDPOINT with no INTERFACE above it. ----
    for k in 1 to 20 loop
      b_e := to_integer(unsigned(n_orphan));
      start_set;
      emit(9, 9, T_CONFIG, b8(16), Z8, b8(1));
      emit(7, 7, T_ENDPOINT, x"81", x"02", Z8);
      end_set;
      chk(to_integer(unsigned(n_orphan)) = b_e + 1,
          "an endpoint appeared before any interface and it was not reported -- it has no owner, and the host will attribute it to whatever interface comes next");
    end loop;

    -- ---- Phase I: ALL 256 bLength VALUES on the first record. ----
    for L in 0 to 255 loop
      b_e := to_integer(unsigned(n_zerolen));
      b_ok := to_integer(unsigned(n_short));
      start_set;
      -- At least two bytes whenever bLength is non-zero, so the TYPE is
      -- always known: SHORT is a statement about a length relative to a
      -- type, and a record truncated before its type byte is an overrun
      -- instead -- a different finding about a different defect.
      if L = 0 then
        emit(L, 1, T_CONFIG, b8(32), Z8, b8(1));
      elsif L > 9 then
        emit(L, 9, T_CONFIG, b8(32), Z8, b8(1));
      elsif L < 2 then
        emit(L, 2, T_CONFIG, b8(32), Z8, b8(1));
      else
        emit(L, L, T_CONFIG, b8(32), Z8, b8(1));
      end if;
      end_set;
      if seen_len(L) = 0 then seen_len(L) := 1; n_len := n_len + 1; end if;
      if L = 0 then
        chk(to_integer(unsigned(n_zerolen)) = b_e + 1,
            "bLength zero was not reported as a zero length");
        chk(to_integer(unsigned(n_short)) = b_ok,
            "bLength zero was classified as SHORT -- zero is a hang and short is a wrong value, and a reader needs to know which symptom to expect");
      elsif L < MIN_CFG then
        chk(to_integer(unsigned(n_short)) = b_ok + 1,
            "a CONFIGURATION below its minimum length was accepted");
        chk(to_integer(unsigned(n_zerolen)) = b_e,
            "a short length was classified as zero");
      else
        chk(to_integer(unsigned(n_zerolen)) = b_e
            and to_integer(unsigned(n_short)) = b_ok,
            "a bLength at or above the type's minimum was rejected");
      end if;
    end loop;

    -- ---- Phase J: EXHAUSTIVE. Every walker state x every input. ----
    for st2 in 0 to 4 loop
      for cb in 0 to 7 loop
        step('0', '0', Z8, '0', '1');
        case st2 is
          when 0 => null;
          when 1 => start_set;
          when 2 => start_set; put(b8(9));
          when 3 => start_set; put(b8(9)); put(T_CONFIG);
          when others => start_set; put(Z8);
        end case;
        chk(state = w_code(walk_state_t'val(st2)),
            "the sweep could not reach the walker state it meant to reach");
        if (cb / 4) mod 2 = 1 then sd_v := '1'; else sd_v := '0'; end if;
        if (cb / 2) mod 2 = 1 then bv_v := '1'; else bv_v := '0'; end if;
        if  cb      mod 2 = 1 then ed_v := '1'; else ed_v := '0'; end if;
        step(sd_v, bv_v, x"20", ed_v, '0');
        step(sd_v, bv_v, x"20", ed_v, '0');
      end loop;
    end loop;

    -- ---- Phase K: random bytes, with set boundaries thrown in ----
    for k in 0 to 29999 loop
      sd_v := rnd_lt(22, 1000);
      bv_v := rnd_lt(78, 100);
      ed_v := rnd_lt(25, 1000);
      step(sd_v, bv_v, rnd_byte, ed_v, '0');
    end loop;

    -- ---- Phase L: and legal sets afterwards. ----
    step('0', '0', Z8, '0', '1');
    b_ok := to_integer(unsigned(n_sets_ok));
    for k in 1 to 60 loop legal_set; end loop;
    chk(to_integer(unsigned(n_sets_ok)) = b_ok + 60,
        "the validator stopped accepting consistent descriptor sets");

    -- ---- Final agreement ----
    chk(to_integer(unsigned(n_desc))     = m_d,   "n_desc disagrees with the model");
    chk(to_integer(unsigned(n_zerolen))  = m_zl,  "n_zerolen disagrees");
    chk(to_integer(unsigned(n_short))    = m_sh,  "n_short disagrees");
    chk(to_integer(unsigned(n_overrun))  = m_ov,  "n_overrun disagrees");
    chk(to_integer(unsigned(n_total))    = m_to,  "n_total disagrees");
    chk(to_integer(unsigned(n_ifcount))  = m_ic,  "n_ifcount disagrees");
    chk(to_integer(unsigned(n_epcount))  = m_ep,  "n_epcount disagrees");
    chk(to_integer(unsigned(n_orphan))   = m_or,  "n_orphan disagrees");
    chk(to_integer(unsigned(n_sets_ok))  = m_sok, "n_sets_ok disagrees");

    chk(n_len = 256, "not every bLength value was driven on the first record");
    chk(n_si  = 40,  "not every walker state was crossed with every input combination");
    chk(to_integer(unsigned(n_zerolen)) > 0, "a zero bLength was never seen");
    chk(to_integer(unsigned(n_short))   > 0, "a short descriptor was never seen");
    chk(to_integer(unsigned(n_overrun)) > 0, "the walk never ran past wTotalLength");
    chk(to_integer(unsigned(n_total))   > 0, "wTotalLength never disagreed with the sum");
    chk(to_integer(unsigned(n_ifcount)) > 0, "bNumInterfaces never disagreed");
    chk(to_integer(unsigned(n_epcount)) > 0, "bNumEndpoints never disagreed");
    chk(to_integer(unsigned(n_orphan))  > 0, "an orphan endpoint was never seen");
    chk(to_integer(unsigned(n_sets_ok)) > 100,
        "too few consistent sets to have tested for false positives");

    write(ln, string'("REACH blength=") & integer'image(n_len) &
              "/256 state-x-input=" & integer'image(n_si) &
              "/40 steps=" & integer'image(n_steps));
    writeline(output, ln);
    write(ln, string'("COUNTERS desc=") & integer'image(to_integer(unsigned(n_desc))) &
              " ok=" & integer'image(to_integer(unsigned(n_sets_ok))) &
              " zerolen=" & integer'image(to_integer(unsigned(n_zerolen))) &
              " short=" & integer'image(to_integer(unsigned(n_short))) &
              " overrun=" & integer'image(to_integer(unsigned(n_overrun))) &
              " total=" & integer'image(to_integer(unsigned(n_total))) &
              " ifcount=" & integer'image(to_integer(unsigned(n_ifcount))) &
              " epcount=" & integer'image(to_integer(unsigned(n_epcount))) &
              " orphan=" & integer'image(to_integer(unsigned(n_orphan))));
    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 sim;

11. Exhaustive Verification

MeasureVerilogSystemVerilogVHDL
bLength values on record 0256 / 256256 / 256256 / 256
walker state x input40 / 4040 / 4040 / 40
Steps451674516745167
Checks executed587910587910587910
descriptors walked225722602242
consistent sets accepted256259262
zero lengths303430
short descriptors202122
walk overruns345376335
wTotalLength mismatches271269270
bNumInterfaces mismatches202020
bNumEndpoints mismatches414240
orphan endpoints202222
ResultPASSPASSPASS

The 256-value sweep is the strongest claim here: it is not "we tried a short one and a long one", it is "for every possible value of that byte, the validator's verdict is the right one, and the two rejection classes are distinguished."

12. Mutation Testing

#MutationVerilogSysVerVHDL
P6bNumEndpoints is never compared, at either site152615271524
P7an orphan endpoint is silently adopted363491483
P1wTotalLength is never compared with the sum336334335
P3a record shorter than its type is walked anyway273405689
P2a zero bLength is accepted178452191
P4the walk is not bounded by wTotalLength122122122
P5bNumInterfaces is never compared838383
—unmutated baseline000

All seven die in all three languages, all counts distinct.

P6 is the largest because bNumEndpoints has to be checked in two places — at the end of the set, and again whenever the next interface begins — and the mutation removes both. Every interface in the run then goes unchecked.

P4 and P5 score identically in all three languages (122 and 83), which by now is a familiar signature: both are detected entirely by directed phases, because random bytes essentially never produce a plausible configuration descriptor whose interface count is merely off by one.

13. Debugging Walkthrough: The Interface Class Nobody Recognises

The report. A composite device enumerates on Linux and fails on Windows. The Windows device manager shows one working interface and one with an unknown class code — 0x91, which is not a class code at all.

Step 1 — is 0x91 in the descriptor? Dump the bytes the device actually returns. Search for 0x91. It is not there. No byte in the buffer is 0x91 at the offset of any bInterfaceClass.

Step 2 — so where did Windows get it? Count bytes by hand. 0x91 appears once, as the third byte of the second endpoint descriptor — wMaxPacketSize, low byte, 0x0091 = 145 bytes.

Step 3 — so Windows is reading the second interface at the wrong offset. By exactly three bytes.

Step 4 — find the three bytes. The first interface descriptor declares bLength = 12. The interface descriptor is defined to be 9 bytes long. The device is emitting 9 bytes and claiming 12.

Step 5 — why Linux works. Linux's parser validates bLength against the descriptor type and rejects the set. It then falls back to a second enumeration attempt that reads the descriptor differently, and happens to succeed. Windows trusts bLength and walks.

Step 6 — and why the report named a field that was correct. wMaxPacketSize was written correctly by the firmware. It was read as bInterfaceClass because the pointer was three bytes ahead, and the pointer was three bytes ahead because of a field in a different record that nobody was looking at.

14. UVM: Validating Descriptors as Transactions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A descriptor set arrives as bytes and is USED as a structure, and those
// are two different objects. The environment needs both: the bytes, because
// that is what the device actually sent, and the structure, because that is
// what the host will act on.
//
// The validator lives between them, and it is the only place that knows
// they can disagree.
class usb_descriptor_item extends uvm_sequence_item;
  `uvm_object_utils(usb_descriptor_item)

  // THE BYTES. Kept verbatim, because the walkthrough in section 13 is
  // impossible without them: "search the raw bytes for the value that was
  // reported" needs the raw bytes.
  rand byte unsigned raw[];

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

  // The three numbers that must agree, computed from the bytes rather than
  // carried alongside them. A structure field that is SET by the generator
  // and then CHECKED by the checker proves only that the generator and the
  // checker agree with each other.
  function int unsigned sum_of_lengths();
    int unsigned p = 0, total = 0;
    while (p < raw.size()) begin
      if (raw[p] == 0) return -1;   // zero length: the walk cannot advance
      total += raw[p];
      p += raw[p];
    end
    return total;
  endfunction

  function int unsigned declared_total();
    if (raw.size() < 4) return 0;
    return {raw[3], raw[2]};        // wTotalLength, little-endian
  endfunction
endclass

class usb_descriptor_checker extends uvm_component;
  `uvm_component_utils(usb_descriptor_checker)

  uvm_analysis_imp #(usb_descriptor_item, usb_descriptor_checker) ap;

  int unsigned n_sets, n_ok;
  int unsigned n_err[string];

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

  function void flag(string cause, string detail);
    n_err[cause]++;
    `uvm_error("DESC", $sformatf("%s: %s", cause, detail))
  endfunction

  function void write(usb_descriptor_item t);
    int unsigned p = 0;
    int unsigned if_seen = 0, if_expected = 0;
    int unsigned ep_seen = 0, ep_expected = 0;
    bit have_if = 0, have_cfg = 0;
    n_sets++;

    while (p < t.raw.size()) begin
      byte unsigned blen, btype;

      // ---- A ZERO LENGTH IS A HANG. The bound on this loop is the whole
      // ---- reason a host does not lock up on a broken device, and it is
      // ---- the reason this check comes first.
      blen = t.raw[p];
      if (blen == 0) begin
        flag("ZEROLEN",
          $sformatf("a record at offset %0d claims bLength 0 -- a host that does not bound its walk does not FAIL on this, it HANGS",
                    p));
        return;
      end

      if (p + 1 >= t.raw.size()) begin
        flag("OVERRUN",
          $sformatf("the set ends inside a record that began at offset %0d", p));
        return;
      end
      btype = t.raw[p+1];

      // ---- Shorter than the type requires: every field read from this
      // ---- record comes from the NEXT one.
      if (blen < min_len(btype)) begin
        flag("SHORT",
          $sformatf("a type-%0d record at offset %0d declares bLength %0d, below the %0d its type requires -- every field the host reads from here comes from the next record, and the error it eventually reports will be against a field that is perfectly correct",
                    btype, p, blen, min_len(btype)));
        return;
      end

      case (btype)
        2: begin  // CONFIGURATION
          have_cfg = 1;
          if_expected = t.raw[p+4];
        end
        4: begin  // INTERFACE
          if (have_if && ep_seen != ep_expected)
            flag("EPCOUNT",
              $sformatf("an interface declared bNumEndpoints %0d and %0d endpoint records followed it",
                        ep_expected, ep_seen));
          have_if = 1;
          if_seen++;
          ep_seen = 0;
          ep_expected = t.raw[p+4];
        end
        5: begin  // ENDPOINT
          if (!have_if)
            flag("ORPHAN",
              $sformatf("an endpoint at offset %0d has no interface above it -- the host will attribute it to whatever interface comes next",
                        p));
          else ep_seen++;
        end
        default: ;
      endcase

      p += blen;
    end

    // ---- The aggregate checks, which cannot be made record by record. ----
    if (have_if && ep_seen != ep_expected)
      // The LAST interface. Every other one is checked when the next
      // begins; this one has no next, and forgetting it is the single most
      // common gap in a hand-written descriptor checker.
      flag("EPCOUNT",
        $sformatf("the last interface declared bNumEndpoints %0d and %0d endpoint records followed it",
                  ep_expected, ep_seen));
    else if (have_cfg && t.declared_total() != p)
      flag("TOTAL",
        $sformatf("wTotalLength says %0d and the bLengths sum to %0d -- the host reads by the first and walks by the second, so it will land %0d bytes into a record and report an error against a field that is correct",
                  t.declared_total(), p, (int'(p) - int'(t.declared_total()))));
    else if (have_cfg && if_seen != if_expected)
      flag("IFCOUNT",
        $sformatf("bNumInterfaces says %0d and %0d interface records are present",
                  if_expected, if_seen));
    else
      n_ok++;
  endfunction

  function int unsigned min_len(byte unsigned t);
    case (t)
      2: return 9;    // CONFIGURATION
      4: return 9;    // INTERFACE
      5: return 7;    // ENDPOINT
      default: return 2;
    endcase
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("DESC", $sformatf("sets=%0d consistent=%0d %p",
                                n_sets, n_ok, n_err), UVM_LOW)

    // A validator that never saw a CONSISTENT set has not been shown to be
    // quiet on legal input, and a validator that is not quiet on legal
    // input gets switched off.
    if (n_ok == 0)
      `uvm_error("COVERAGE",
        "no descriptor set was ever accepted -- either nothing was driven or the validator rejects everything, and a validator that rejects everything is one nobody leaves enabled")
  endfunction
endclass

15. Common Misconceptions

"The field named in the error is the field that is wrong." If the parser is misaligned, every field name in the report is an offset into a structure that is not there.

"bLength too small is just a short read." Every field after it comes from the next record, and the report names a field that was written correctly.

"bLength = 0 is a variant of too-short." It is a hang, not a wrong value, and the symptoms are nothing alike.

"wTotalLength and the sum of bLength are the same number." They are two fields that must agree, computed by different parts of the firmware, and they drift independently.

"The last interface's endpoint count is checked like the others." Every other interface is checked when the next begins. The last one has no next.

"An orphan endpoint is harmless." The host attributes it to whatever interface comes next, silently changing that interface's shape.

"Hosts validate descriptors, so a bad one fails safely." They validate differently. Linux rejected the set in §13; Windows walked it.

"One error per defect is automatic." A walk that has stopped still has wrong aggregate counters, and re-checking them reports a second bug that does not exist.

16. Exercises

1. A configuration set declares wTotalLength = 34 and contains records of lengths 9, 9, 7, 7. Work out which byte of which record the host reads as bDescriptorType for the record after the end, and say what it will conclude.

2. P4 and P5 score exactly 122 and 83 in all three languages. Identify the directed phases responsible and explain why random bytes essentially never exercise them.

3. Show that a host bounding its walk by wTotalLength alone still hangs on bLength = 0, and write the second bound that fixes it.

4. The end-of-set checks are skipped in W_DEAD. Construct the descriptor set that produces two errors for one defect without that exclusion, and name both.

5. Add support for a class-specific descriptor of a type the validator does not know. What must it check, what must it not check, and why is "ignore it" the wrong answer for bLength?

6. The UVM checker recomputes every number from raw[]. Write the version that trusts a stored field instead and construct the device bug it cannot see.

17. Summary

IdeaWhy it matters
A descriptor set is a self-describing treeits structure is implied by order and three counts
The buffer is described three timeswTotalLength, bNumInterfaces, bNumEndpoints
The host reads by one field and walks by anotherso disagreement misaligns everything after it
The error names a correct fieldsymptom and cause are separated by a variable gap
bLength = 0 is a hang, not a wrong valuethe walk advances by zero, for ever
Short is a different bugthe record cannot hold the fields its type defines
The last interface has no next interfaceso its endpoint count needs its own check
An orphan endpoint is adopted by the next interfacesilently changing that interface's shape
A dead walk must not re-reportor one defect becomes two
One injected defect at a timeor the suite measures whichever check is first
Recompute from the bytesa stored field proves the generator agrees with the checker
256/256 bLength values, 40/40 state x input7 mutations, all killed in 3 languages

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o dv_v.out dv_v.v dv_v_tb.v && ./dv_v.out
SystemVerilogiverilog -g2012 -o dv_sv.out dv_sv.sv dv_sv_tb.sv && ./dv_sv.out
VHDL-2008 analysenvc --std=2008 -a dv_vhdl.vhd dv_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_dv_vhdl
VHDL-2008 runnvc --std=2008 -r tb_dv_vhdl
One mutationiverilog -g2005 -DMUT_P1 -o mm dv_v_mut.v dv_v_tb.v && ./mm

All three implementations pass with 0 errors: every one of the 256 possible bLength values driven on the first record with the verdict checked and the two rejection classes distinguished, every walker state crossed with every input, and 180 consistent sets of three shapes accepted with no false positives.


Chapter 25.3 — Endpoint Problems moves from the descriptor to the endpoint it describes. The distinction that defines it is one this module has already touched twice: a NAK is not an error and a STALL is not a NAK. One is flow control working, one is firmware saying no permanently, and a health monitor that treats them alike either floods the log on a busy bus or misses a wedged endpoint entirely.

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.