Skip to content
VLSI Mentor

USB · Module 24

USB VIP Usage

A VIP is a second implementation of the specification, so where it and the design disagree one of them is wrong — and the adapter's job is to make that visible, never to resolve it in a shim.

Every checking component so far has been one you wrote. This chapter is about the one you did not.

1. A VIP Is a Second Implementation of the Specification

That is the whole value of it. Two independent readings of the same document, watching the same wires, and where they disagree one of them is wrong.

Neither can tell you which, because each is reasoning from its own model — and that is fine. "These two disagree, here, on this packet" is already the most useful thing anybody will learn that day.

2. The Job the Adapter Must Not Do

An adapter sits between the VIP and the design because their interfaces never match exactly: different field widths, different event granularity, a cycle of pipelining here, a differently-named signal there. Writing that translation is legitimate work.

What is not legitimate is the step that always follows:

"the VIP flags this as an error but our device is allowed to do it, so the adapter filters it out"

So this block compares the two views and reports the difference. It has a permissive mode — because sometimes you genuinely must get through the afternoon — and in permissive mode it counts what it masked. A permissive run cannot come back clean; it comes back with a number, and the number is how many specification arguments nobody has had yet.

3. A VIP's Coverage Is the VIP's Model's Coverage

This is worth saying plainly because VIP coverage reports get quoted as if they were project coverage.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   A VIP at 100% has exercised every bin IT declares:

     the bins ITS authors thought of
     for the specification revision THEY targeted
     with the configurations THEY support

   It says nothing about the model in chapter 24.4, and in
   particular nothing about the combinations your device
   supports and its authors had never heard of.

Version skew is the usual shape of this. The VIP implements revision X; the device targets revision Y; the disagreements cluster on the delta — which is exactly the region with the least review behind it on both sides.

4. The Two Sides Do Not Speak at the Same Time

A behavioural VIP reports a packet when it has decoded it. A pipelined design reports the same packet some cycles later. So the adapter aligns them with a small queue on each side rather than comparing cycle by cycle.

And the alignment window is bounded.

5. What We Are Building

usb_vip_adapter — two views, aligned and compared, never reconciled

A VIP adapter. The VIP's decoded view and the design's decoded view each enter a short alignment queue. The oldest pair from the two queues is compared: identical PID and identical interpretation is agreement, a difference is a disagreement that fails in strict mode and is masked and counted in permissive mode. If either queue runs more than SKEW_MAX events ahead, alignment is lost and is reported once.VIP's viewa second readingdesign's viewthe same wiresalign queuebounded by SKEW_MAXalign queuebounded by SKEW_MAXcompare oldestPID, then meaningALIGNMENT LOSTreported onceAGREEboth readings matchDISAGREEstrict: the run failsMASKEDpermissive: countedpast boundstrictpermissive12
Each side's events go into a short alignment queue; the oldest pair is compared. Disagreement is reported in strict mode and masked-and-counted in permissive mode. Either queue running past SKEW_MAX means alignment is lost, which is reported once and flushed rather than tolerated.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  usb_vip_adapter  #(QD = 8, QW = 3, SKEW_MAX = 4)

  the VIP's decoded view     the design's decoded view
  ----------------------     -------------------------
  vip_valid / vip_pid        dut_valid / dut_pid
  vip_view                   dut_view

  strict   1: a disagreement FAILS
           0: it is MASKED -- and COUNTED

  outputs   agree_pulse / dis_pulse / masked_pulse
            dis_code  PID / VIEW / SKEW / UNPAIRED
            skew      how far apart the two sides are

  n_agree n_pid_dis n_view_dis n_skew n_masked n_unpaired

dis_code separates two things that get conflated. A PID disagreement means the two sides decoded different packets — usually a decode bug or a lost event. A VIEW disagreement means they decoded the same packet and read it differently, and that is always a specification reading. Those go to different people.

6. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_vip_adapter -- integrating verification IP you did not write, and the
// one job the adapter must not do.
//
// A VIP IS A SECOND IMPLEMENTATION OF THE SPECIFICATION
//
// That is the whole value of it. Two independent readings of the same
// document, watching the same wires, and where they disagree ONE OF THEM IS
// WRONG. Neither can tell you which, because each is reasoning from its own
// model -- and that is fine, because "these two disagree, here, on this
// packet" is already the most useful thing anybody will learn that day.
//
// THE JOB THE ADAPTER MUST NOT DO
//
// An adapter sits between the VIP and the design because their interfaces
// never match exactly: different field widths, different event granularity,
// a cycle of pipelining here, a differently-named signal there. Writing that
// translation is legitimate work.
//
// What is not legitimate is the step that always follows:
//
//     "the VIP flags this as an error but our device is
//      allowed to do it, so the adapter filters it out"
//
// That sentence is sometimes true. It is a specification argument, and it
// has to be WON -- written down, cited, and reviewed -- not implemented in a
// shim by whoever was unblocking the regression that afternoon. Once it is
// in the shim, the disagreement is invisible, and so is every future
// disagreement of the same shape.
//
//     AN ADAPTER MAKES A DISAGREEMENT VISIBLE.
//     IT DOES NOT RESOLVE ONE.
//
// So this block compares the two views and reports the difference. It has a
// permissive mode -- because sometimes you genuinely must get through the
// afternoon -- and in permissive mode it COUNTS what it masked. A permissive
// run cannot come back clean; it comes back with a number.
//
// A VIP'S COVERAGE IS THE VIP'S MODEL'S COVERAGE
//
// This is worth saying plainly because VIP coverage reports are quoted as if
// they were project coverage. A VIP at 100% has exercised every bin IT
// declares, which are the bins ITS authors thought of, for the specification
// revision THEY targeted. It says nothing about the bins in chapter 24.4's
// model, and in particular it says nothing about the combinations your
// device supports and the VIP's authors had never heard of.
//
// THE TWO SIDES DO NOT SPEAK AT THE SAME TIME
//
// A behavioural VIP reports a packet when it has decoded it; a pipelined
// design reports the same packet some cycles later. So the adapter aligns
// them with a small queue on each side rather than comparing cycle by cycle.
//
// And the alignment window is BOUNDED. When one side runs SKEW_MAX events
// ahead of the other, they are not skewed any more -- they have lost
// alignment, and every comparison after that point is between unrelated
// events. Widening the window is the reflex and it is wrong: it converts a
// loud failure into a quiet stream of nonsense comparisons.
module usb_vip_adapter #(
  parameter integer QD       = 8,   // alignment queue depth
  parameter integer QW       = 3,   // log2(QD)
  parameter integer SKEW_MAX = 4    // events one side may run ahead
) (
  input  wire       clk,
  input  wire       rst_n,

  // ---- the VIP's decoded view of the bus ----
  input  wire       vip_valid,
  input  wire [3:0] vip_pid,
  input  wire [1:0] vip_view,

  // ---- the design's decoded view of the SAME bus ----
  input  wire       dut_valid,
  input  wire [3:0] dut_pid,
  input  wire [1:0] dut_view,

  input  wire       strict,     // 1: a disagreement fails. 0: it is COUNTED.
  input  wire       eot,

  output wire [QW:0] vip_depth,
  output wire [QW:0] dut_depth,
  output wire [QW:0] skew,
  output wire        agree_pulse,
  output wire        dis_pulse,
  output wire [2:0]  dis_code,
  output wire        masked_pulse,

  output reg [31:0] n_agree,
  output reg [31:0] n_pid_dis,
  output reg [31:0] n_view_dis,
  output reg [31:0] n_skew,
  output reg [31:0] n_masked,
  output reg [31:0] n_unpaired
);

  localparam [2:0] D_NONE     = 3'd0,
                   D_PID      = 3'd1,  // the two sides decoded a different PID
                   D_VIEW     = 3'd2,  // same PID, different interpretation
                   D_SKEW     = 3'd3,  // alignment lost
                   D_UNPAIRED = 3'd4;  // an event only one side ever saw

  reg [3:0] vp_pid [0:QD-1];
  reg [1:0] vp_vw  [0:QD-1];
  reg [3:0] dt_pid [0:QD-1];
  reg [1:0] dt_vw  [0:QD-1];
  reg [QW:0] vp_n, dt_n;

  reg [2:0] code_r;
  reg       agr_r, dis_r, msk_r;

  assign vip_depth    = vp_n;
  assign dut_depth    = dt_n;
  // Only one queue can be non-empty after a comparison, so the skew is
  // simply whichever one still holds events.
  assign skew         = (vp_n > dt_n) ? vp_n : dt_n;
  assign agree_pulse  = agr_r;
  assign dis_pulse    = dis_r;
  assign dis_code     = code_r;
  assign masked_pulse = msk_r;

  integer i;
  reg [3:0] nvp_pid [0:QD-1];
  reg [1:0] nvp_vw  [0:QD-1];
  reg [3:0] ndt_pid [0:QD-1];
  reg [1:0] ndt_vw  [0:QD-1];
  reg [QW:0] nvp_n, ndt_n;
  reg [2:0]  ncode;
  reg        nagr, ndis, nmsk, nunp;
  reg        differ;

  always @* begin
    for (i = 0; i < QD; i = i + 1) begin
      nvp_pid[i] = vp_pid[i]; nvp_vw[i] = vp_vw[i];
      ndt_pid[i] = dt_pid[i]; ndt_vw[i] = dt_vw[i];
    end
    nvp_n = vp_n; ndt_n = dt_n;
    ncode = D_NONE;
    nagr = 1'b0; ndis = 1'b0; nmsk = 1'b0; nunp = 1'b0;
    differ = 1'b0;

    if (eot) begin
      // ---- THE DRAIN. An event only one side ever reported. ----
      //
      // Not "skew that had not settled": the run is over. One of the two
      // implementations saw something the other never did, which is exactly
      // the class of finding a second implementation exists to produce.
      if (vp_n != 0) begin
        for (i = 0; i < QD - 1; i = i + 1) begin
          nvp_pid[i] = nvp_pid[i+1]; nvp_vw[i] = nvp_vw[i+1];
        end
        nvp_n = vp_n - 1'b1;
        ndis  = 1'b1; ncode = D_UNPAIRED; nunp = 1'b1;
      end else if (dt_n != 0) begin
        for (i = 0; i < QD - 1; i = i + 1) begin
          ndt_pid[i] = ndt_pid[i+1]; ndt_vw[i] = ndt_vw[i+1];
        end
        ndt_n = dt_n - 1'b1;
        ndis  = 1'b1; ncode = D_UNPAIRED; nunp = 1'b1;
      end
    end else begin
      // ---- 1. Both sides have an event: compare the OLDEST pair. ----
      if ((vp_n != 0) && (dt_n != 0)) begin
        if (vp_pid[0] !== dt_pid[0]) begin
          differ = 1'b1; ncode = D_PID;
        end else if (vp_vw[0] !== dt_vw[0]) begin
          // ---- Same PID, different INTERPRETATION. ----
          //
          // This is the disagreement worth having a VIP for. Both sides
          // agree on what arrived and disagree on what it MEANS -- which is
          // always a specification reading, and is the case an adapter is
          // most often asked to paper over.
          differ = 1'b1; ncode = D_VIEW;
        end else begin
          nagr = 1'b1;
        end

        if (differ) begin
          if (strict) begin
            ndis = 1'b1;
          end else begin
            // ---- PERMISSIVE. Masked, and COUNTED. ----
            //
            // The count is the point. A permissive run does not come back
            // clean; it comes back with a number, and the number is how
            // many specification arguments nobody has had yet.
            nmsk = 1'b1;
          end
        end

        for (i = 0; i < QD - 1; i = i + 1) begin
          nvp_pid[i] = nvp_pid[i+1]; nvp_vw[i] = nvp_vw[i+1];
          ndt_pid[i] = ndt_pid[i+1]; ndt_vw[i] = ndt_vw[i+1];
        end
        nvp_n = nvp_n - 1'b1;
        ndt_n = ndt_n - 1'b1;
      end

      // ---- 2. Enqueue whatever arrived this cycle. ----
      if (vip_valid && (nvp_n < QD[QW:0])) begin
        nvp_pid[nvp_n] = vip_pid;
        nvp_vw[nvp_n]  = vip_view;
        nvp_n = nvp_n + 1'b1;
      end
      if (dut_valid && (ndt_n < QD[QW:0])) begin
        ndt_pid[ndt_n] = dut_pid;
        ndt_vw[ndt_n]  = dut_view;
        ndt_n = ndt_n + 1'b1;
      end

      // ---- 3. THE ALIGNMENT BOUND. ----
      //
      // One side running SKEW_MAX events ahead of the other is not skew any
      // more: alignment is lost, and every comparison after this point is
      // between unrelated events. The queues are flushed so that the loss
      // is reported ONCE rather than as a stream of nonsense PID
      // disagreements.
      //
      // Widening SKEW_MAX is the reflex and it is the wrong move: it
      // converts a loud failure into a quiet stream of garbage.
      if ((nvp_n > SKEW_MAX[QW:0]) || (ndt_n > SKEW_MAX[QW:0])) begin
        nvp_n = {(QW+1){1'b0}};
        ndt_n = {(QW+1){1'b0}};
        ndis  = 1'b1;
        nmsk  = 1'b0;
        ncode = D_SKEW;
        nagr  = 1'b0;
      end
    end
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (i = 0; i < QD; i = i + 1) begin
        vp_pid[i] <= 4'd0; vp_vw[i] <= 2'd0;
        dt_pid[i] <= 4'd0; dt_vw[i] <= 2'd0;
      end
      vp_n       <= {(QW+1){1'b0}};
      dt_n       <= {(QW+1){1'b0}};
      code_r     <= D_NONE;
      agr_r      <= 1'b0;
      dis_r      <= 1'b0;
      msk_r      <= 1'b0;
      n_agree    <= 32'd0;
      n_pid_dis  <= 32'd0;
      n_view_dis <= 32'd0;
      n_skew     <= 32'd0;
      n_masked   <= 32'd0;
      n_unpaired <= 32'd0;
    end else begin
      for (i = 0; i < QD; i = i + 1) begin
        vp_pid[i] <= nvp_pid[i]; vp_vw[i] <= nvp_vw[i];
        dt_pid[i] <= ndt_pid[i]; dt_vw[i] <= ndt_vw[i];
      end
      vp_n   <= nvp_n;
      dt_n   <= ndt_n;
      code_r <= ncode;
      agr_r  <= nagr;
      dis_r  <= ndis;
      msk_r  <= nmsk;

      if (nagr) n_agree <= n_agree + 32'd1;
      if (nmsk) n_masked <= n_masked + 32'd1;

      // A masked disagreement still increments its CAUSE counter. The
      // permissive switch decides whether the run fails; it does not decide
      // whether the disagreement happened.
      if (ndis || nmsk) begin
        case (ncode)
          D_PID:      n_pid_dis  <= n_pid_dis  + 32'd1;
          D_VIEW:     n_view_dis <= n_view_dis + 32'd1;
          D_SKEW:     n_skew     <= n_skew     + 32'd1;
          D_UNPAIRED: n_unpaired <= n_unpaired + 32'd1;
          default: ;
        endcase
      end
    end
  end
endmodule

7. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_vip_adapter -- integrating verification IP you did not write, and the
// one job the adapter must not do.
//
// A VIP IS A SECOND IMPLEMENTATION OF THE SPECIFICATION
//
// That is the whole value of it. Two independent readings of the same
// document, watching the same wires, and where they disagree ONE OF THEM IS
// WRONG. Neither can tell you which, because each is reasoning from its own
// model -- and that is fine, because "these two disagree, here, on this
// packet" is already the most useful thing anybody will learn that day.
//
// THE JOB THE ADAPTER MUST NOT DO
//
// An adapter sits between the VIP and the design because their interfaces
// never match exactly: different field widths, different event granularity,
// a cycle of pipelining here, a differently-named signal there. Writing that
// translation is legitimate work.
//
// What is not legitimate is the step that always follows:
//
//     "the VIP flags this as an error but our device is
//      allowed to do it, so the adapter filters it out"
//
// That sentence is sometimes true. It is a specification argument, and it
// has to be WON -- written down, cited, and reviewed -- not implemented in a
// shim by whoever was unblocking the regression that afternoon. Once it is
// in the shim, the disagreement is invisible, and so is every future
// disagreement of the same shape.
//
//     AN ADAPTER MAKES A DISAGREEMENT VISIBLE.
//     IT DOES NOT RESOLVE ONE.
//
// So this block compares the two views and reports the difference. It has a
// permissive mode -- because sometimes you genuinely must get through the
// afternoon -- and in permissive mode it COUNTS what it masked. A permissive
// run cannot come back clean; it comes back with a number.
//
// A VIP'S COVERAGE IS THE VIP'S MODEL'S COVERAGE
//
// This is worth saying plainly because VIP coverage reports are quoted as if
// they were project coverage. A VIP at 100% has exercised every bin IT
// declares, which are the bins ITS authors thought of, for the specification
// revision THEY targeted. It says nothing about the bins in chapter 24.4's
// model, and in particular it says nothing about the combinations your
// device supports and the VIP's authors had never heard of.
//
// THE TWO SIDES DO NOT SPEAK AT THE SAME TIME
//
// A behavioural VIP reports a packet when it has decoded it; a pipelined
// design reports the same packet some cycles later. So the adapter aligns
// them with a small queue on each side rather than comparing cycle by cycle.
//
// And the alignment window is BOUNDED. When one side runs SKEW_MAX events
// ahead of the other, they are not skewed any more -- they have lost
// alignment, and every comparison after that point is between unrelated
// events. Widening the window is the reflex and it is wrong: it converts a
// loud failure into a quiet stream of nonsense comparisons.
package usb_vip_pkg;
  // The four ways two independent readings of the same bus can differ.
  // D_VIEW is the one worth owning a VIP for: both sides agree on what
  // arrived and disagree on what it MEANS, which is always a specification
  // reading rather than a decode bug.
  typedef enum logic [2:0] {
    D_NONE     = 3'd0,
    D_PID      = 3'd1,   // the two sides decoded a different PID
    D_VIEW     = 3'd2,   // same PID, different interpretation
    D_SKEW     = 3'd3,   // alignment lost
    D_UNPAIRED = 3'd4    // an event only one side ever saw
  } dis_e;
endpackage

module usb_vip_adapter
  import usb_vip_pkg::*;
 #(
  parameter int QD       = 8,   // alignment queue depth
  parameter int QW       = 3,   // log2(QD)
  parameter int SKEW_MAX = 4    // events one side may run ahead
) (
  input  logic      clk,
  input  logic      rst_n,

  // ---- the VIP's decoded view of the bus ----
  input  logic      vip_valid,
  input  logic [3:0] vip_pid,
  input  logic [1:0] vip_view,

  // ---- the design's decoded view of the SAME bus ----
  input  logic      dut_valid,
  input  logic [3:0] dut_pid,
  input  logic [1:0] dut_view,

  input  logic      strict,     // 1: a disagreement fails. 0: it is COUNTED.
  input  logic      eot,

  output logic [QW:0] vip_depth,
  output logic [QW:0] dut_depth,
  output logic [QW:0] skew,
  output logic       agree_pulse,
  output logic       dis_pulse,
  output dis_e        dis_code,
  output logic       masked_pulse,

  output logic [31:0] n_agree,
  output logic [31:0] n_pid_dis,
  output logic [31:0] n_view_dis,
  output logic [31:0] n_skew,
  output logic [31:0] n_masked,
  output logic [31:0] n_unpaired
);

  logic [3:0] vp_pid [QD];
  logic [1:0] vp_vw  [QD];
  logic [3:0] dt_pid [QD];
  logic [1:0] dt_vw  [QD];
  logic [QW:0] vp_n, dt_n;

  dis_e code_r;
  logic agr_r, dis_r, msk_r;

  assign vip_depth    = vp_n;
  assign dut_depth    = dt_n;
  // Only one queue can be non-empty after a comparison, so the skew is
  // simply whichever one still holds events.
  assign skew         = (vp_n > dt_n) ? vp_n : dt_n;
  assign agree_pulse  = agr_r;
  assign dis_pulse    = dis_r;
  assign dis_code     = code_r;
  assign masked_pulse = msk_r;

  int          i;
  logic [3:0]  nvp_pid [QD];
  logic [1:0]  nvp_vw  [QD];
  logic [3:0]  ndt_pid [QD];
  logic [1:0]  ndt_vw  [QD];
  logic [QW:0] nvp_n, ndt_n;
  dis_e        ncode;
  logic        nagr, ndis, nmsk, nunp;
  logic        differ;

  always_comb begin
    for (i = 0; i < QD; i++) begin
      nvp_pid[i] = vp_pid[i]; nvp_vw[i] = vp_vw[i];
      ndt_pid[i] = dt_pid[i]; ndt_vw[i] = dt_vw[i];
    end
    nvp_n = vp_n; ndt_n = dt_n;
    ncode = D_NONE;
    nagr = 1'b0; ndis = 1'b0; nmsk = 1'b0; nunp = 1'b0;
    differ = 1'b0;

    if (eot) begin
      // ---- THE DRAIN. An event only one side ever reported. ----
      //
      // Not "skew that had not settled": the run is over. One of the two
      // implementations saw something the other never did, which is exactly
      // the class of finding a second implementation exists to produce.
      if (vp_n != 0) begin
        for (i = 0; i < QD - 1; i++) begin
          nvp_pid[i] = nvp_pid[i+1]; nvp_vw[i] = nvp_vw[i+1];
        end
        nvp_n = vp_n - 1'b1;
        ndis  = 1'b1; ncode = D_UNPAIRED; nunp = 1'b1;
      end else if (dt_n != 0) begin
        for (i = 0; i < QD - 1; i++) begin
          ndt_pid[i] = ndt_pid[i+1]; ndt_vw[i] = ndt_vw[i+1];
        end
        ndt_n = dt_n - 1'b1;
        ndis  = 1'b1; ncode = D_UNPAIRED; nunp = 1'b1;
      end
    end else begin
      // ---- 1. Both sides have an event: compare the OLDEST pair. ----
      if ((vp_n != 0) && (dt_n != 0)) begin
        if (vp_pid[0] !== dt_pid[0]) begin
          differ = 1'b1; ncode = D_PID;
        end else if (vp_vw[0] !== dt_vw[0]) begin
          // ---- Same PID, different INTERPRETATION. ----
          //
          // This is the disagreement worth having a VIP for. Both sides
          // agree on what arrived and disagree on what it MEANS -- which is
          // always a specification reading, and is the case an adapter is
          // most often asked to paper over.
          differ = 1'b1; ncode = D_VIEW;
        end else begin
          nagr = 1'b1;
        end

        if (differ) begin
          if (strict) begin
            ndis = 1'b1;
          end else begin
            // ---- PERMISSIVE. Masked, and COUNTED. ----
            //
            // The count is the point. A permissive run does not come back
            // clean; it comes back with a number, and the number is how
            // many specification arguments nobody has had yet.
            nmsk = 1'b1;
          end
        end

        for (i = 0; i < QD - 1; i++) begin
          nvp_pid[i] = nvp_pid[i+1]; nvp_vw[i] = nvp_vw[i+1];
          ndt_pid[i] = ndt_pid[i+1]; ndt_vw[i] = ndt_vw[i+1];
        end
        nvp_n = nvp_n - 1'b1;
        ndt_n = ndt_n - 1'b1;
      end

      // ---- 2. Enqueue whatever arrived this cycle. ----
      if (vip_valid && (nvp_n < (QW+1)'(QD))) begin
        nvp_pid[nvp_n] = vip_pid;
        nvp_vw[nvp_n]  = vip_view;
        nvp_n = nvp_n + 1'b1;
      end
      if (dut_valid && (ndt_n < (QW+1)'(QD))) begin
        ndt_pid[ndt_n] = dut_pid;
        ndt_vw[ndt_n]  = dut_view;
        ndt_n = ndt_n + 1'b1;
      end

      // ---- 3. THE ALIGNMENT BOUND. ----
      //
      // One side running SKEW_MAX events ahead of the other is not skew any
      // more: alignment is lost, and every comparison after this point is
      // between unrelated events. The queues are flushed so that the loss
      // is reported ONCE rather than as a stream of nonsense PID
      // disagreements.
      //
      // Widening SKEW_MAX is the reflex and it is the wrong move: it
      // converts a loud failure into a quiet stream of garbage.
      if ((nvp_n > (QW+1)'(SKEW_MAX)) || (ndt_n > (QW+1)'(SKEW_MAX))) begin
        nvp_n = '0;
        ndt_n = '0;
        ndis  = 1'b1;
        nmsk  = 1'b0;
        ncode = D_SKEW;
        nagr  = 1'b0;
      end
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (i = 0; i < QD; i++) begin
        vp_pid[i] <= 4'd0; vp_vw[i] <= 2'd0;
        dt_pid[i] <= 4'd0; dt_vw[i] <= 2'd0;
      end
      vp_n       <= '0;
      dt_n       <= '0;
      code_r     <= D_NONE;
      agr_r      <= 1'b0;
      dis_r      <= 1'b0;
      msk_r      <= 1'b0;
      n_agree    <= 32'd0;
      n_pid_dis  <= 32'd0;
      n_view_dis <= 32'd0;
      n_skew     <= 32'd0;
      n_masked   <= 32'd0;
      n_unpaired <= 32'd0;
    end else begin
      for (i = 0; i < QD; i++) begin
        vp_pid[i] <= nvp_pid[i]; vp_vw[i] <= nvp_vw[i];
        dt_pid[i] <= ndt_pid[i]; dt_vw[i] <= ndt_vw[i];
      end
      vp_n   <= nvp_n;
      dt_n   <= ndt_n;
      code_r <= ncode;
      agr_r  <= nagr;
      dis_r  <= ndis;
      msk_r  <= nmsk;

      if (nagr) n_agree <= n_agree + 32'd1;
      if (nmsk) n_masked <= n_masked + 32'd1;

      // A masked disagreement still increments its CAUSE counter. The
      // permissive switch decides whether the run fails; it does not decide
      // whether the disagreement happened.
      if (ndis || nmsk) begin
        case (ncode)
          D_PID:      n_pid_dis  <= n_pid_dis  + 32'd1;
          D_VIEW:     n_view_dis <= n_view_dis + 32'd1;
          D_SKEW:     n_skew     <= n_skew     + 32'd1;
          D_UNPAIRED: n_unpaired <= n_unpaired + 32'd1;
          default: ;
        endcase
      end
    end
  end
endmodule

8. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb_vip_adapter -- integrating verification IP you did not write, and the
-- one job the adapter must not do.
--
-- A VIP IS A SECOND IMPLEMENTATION OF THE SPECIFICATION
--
-- That is the whole value of it. Two independent readings of the same
-- document, watching the same wires, and where they disagree ONE OF THEM IS
-- WRONG. Neither can tell you which, because each is reasoning from its own
-- model -- and that is fine, because "these two disagree, here, on this
-- packet" is already the most useful thing anybody will learn that day.
--
-- THE JOB THE ADAPTER MUST NOT DO
--
-- An adapter sits between the VIP and the design because their interfaces
-- never match exactly: different field widths, different event granularity,
-- a cycle of pipelining here, a differently-named signal there. Writing that
-- translation is legitimate work.
--
-- What is not legitimate is the step that always follows:
--
--     "the VIP flags this as an error but our device is
--      allowed to do it, so the adapter filters it out"
--
-- That sentence is sometimes true. It is a specification argument, and it
-- has to be WON -- written down, cited, and reviewed -- not implemented in a
-- shim by whoever was unblocking the regression that afternoon. Once it is
-- in the shim, the disagreement is invisible, and so is every future
-- disagreement of the same shape.
--
--     AN ADAPTER MAKES A DISAGREEMENT VISIBLE.
--     IT DOES NOT RESOLVE ONE.
--
-- So this block compares the two views and reports the difference. It has a
-- permissive mode -- because sometimes you genuinely must get through the
-- afternoon -- and in permissive mode it COUNTS what it masked. A permissive
-- run cannot come back clean; it comes back with a number.
--
-- A VIP'S COVERAGE IS THE VIP'S MODEL'S COVERAGE
--
-- This is worth saying plainly because VIP coverage reports are quoted as if
-- they were project coverage. A VIP at 100% has exercised every bin IT
-- declares, which are the bins ITS authors thought of, for the specification
-- revision THEY targeted. It says nothing about the bins in chapter 24.4's
-- model, and in particular it says nothing about the combinations your
-- device supports and the VIP's authors had never heard of.
--
-- THE TWO SIDES DO NOT SPEAK AT THE SAME TIME
--
-- A behavioural VIP reports a packet when it has decoded it; a pipelined
-- design reports the same packet some cycles later. So the adapter aligns
-- them with a small queue on each side rather than comparing cycle by cycle.
--
-- And the alignment window is BOUNDED. When one side runs SKEW_MAX events
-- ahead of the other, they are not skewed any more -- they have lost
-- alignment, and every comparison after that point is between unrelated
-- events. Widening the window is the reflex and it is wrong: it converts a
-- loud failure into a quiet stream of nonsense comparisons.
library ieee;
use ieee.std_logic_1164.all;

package usb_vip_pkg is
  -- The four ways two independent readings of the same bus can differ.
  -- D_VIEW is the one worth owning a VIP for: both sides agree on what
  -- arrived and disagree on what it MEANS, which is always a specification
  -- reading rather than a decode bug.
  type dis_t is (D_NONE, D_PID, D_VIEW, D_SKEW, D_UNPAIRED);
  function d_code (d : dis_t) return std_logic_vector;
end package usb_vip_pkg;

package body usb_vip_pkg is
  function d_code (d : dis_t) return std_logic_vector is
  begin
    case d is
      when D_NONE     => return "000";
      when D_PID      => return "001";
      when D_VIEW     => return "010";
      when D_SKEW     => return "011";
      when D_UNPAIRED => return "100";
    end case;
  end function;
end package body usb_vip_pkg;

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

entity usb_vip_adapter is
  generic (
    QD       : integer := 8;   -- alignment queue depth
    QW       : integer := 3;   -- log2(QD)
    SKEW_MAX : integer := 4    -- events one side may run ahead
  );
  port (
    clk          : in  std_logic;
    rst_n        : in  std_logic;

    -- ---- the VIP's decoded view of the bus ----
    vip_valid    : in  std_logic;
    vip_pid      : in  std_logic_vector(3 downto 0);
    vip_view     : in  std_logic_vector(1 downto 0);

    -- ---- the design's decoded view of the SAME bus ----
    dut_valid    : in  std_logic;
    dut_pid      : in  std_logic_vector(3 downto 0);
    dut_view     : in  std_logic_vector(1 downto 0);

    strict       : in  std_logic;   -- 1: fails. 0: the disagreement is COUNTED.
    eot          : in  std_logic;

    vip_depth    : out std_logic_vector(QW downto 0);
    dut_depth    : out std_logic_vector(QW downto 0);
    skew         : out std_logic_vector(QW downto 0);
    agree_pulse  : out std_logic;
    dis_pulse    : out std_logic;
    dis_code     : out std_logic_vector(2 downto 0);
    masked_pulse : out std_logic;

    n_agree      : out std_logic_vector(31 downto 0);
    n_pid_dis    : out std_logic_vector(31 downto 0);
    n_view_dis   : out std_logic_vector(31 downto 0);
    n_skew       : out std_logic_vector(31 downto 0);
    n_masked     : out std_logic_vector(31 downto 0);
    n_unpaired   : out std_logic_vector(31 downto 0)
  );
end entity usb_vip_adapter;

architecture rtl of usb_vip_adapter is

  type pid_arr is array (0 to QD-1) of std_logic_vector(3 downto 0);
  type vw_arr  is array (0 to QD-1) of std_logic_vector(1 downto 0);

  signal vp_pid : pid_arr := (others => (others => '0'));
  signal vp_vw  : vw_arr  := (others => (others => '0'));
  signal dt_pid : pid_arr := (others => (others => '0'));
  signal dt_vw  : vw_arr  := (others => (others => '0'));
  signal vp_n, dt_n : unsigned(QW downto 0) := (others => '0');

  signal code_r : dis_t := D_NONE;
  signal agr_r, dis_r, msk_r : std_logic := '0';

  -- 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_ag, c_pd, c_vd : unsigned(31 downto 0) := (others => '0');
  signal c_sk, c_mk, c_up : unsigned(31 downto 0) := (others => '0');

begin

  vip_depth <= std_logic_vector(vp_n);
  dut_depth <= std_logic_vector(dt_n);
  -- Only one queue can be non-empty after a comparison, so the skew is
  -- simply whichever one still holds events.
  skew <= std_logic_vector(vp_n) when vp_n > dt_n else std_logic_vector(dt_n);
  agree_pulse  <= agr_r;
  dis_pulse    <= dis_r;
  dis_code     <= d_code(code_r);
  masked_pulse <= msk_r;

  n_agree    <= std_logic_vector(c_ag);
  n_pid_dis  <= std_logic_vector(c_pd);
  n_view_dis <= std_logic_vector(c_vd);
  n_skew     <= std_logic_vector(c_sk);
  n_masked   <= std_logic_vector(c_mk);
  n_unpaired <= std_logic_vector(c_up);

  process (clk, rst_n)
    variable nvp_pid, ndt_pid : pid_arr;
    variable nvp_vw,  ndt_vw  : vw_arr;
    variable nvp_n, ndt_n : unsigned(QW downto 0);
    variable ncode : dis_t;
    variable nagr, ndis, nmsk, dif : std_logic;
  begin
    if rst_n = '0' then
      vp_pid <= (others => (others => '0'));
      vp_vw  <= (others => (others => '0'));
      dt_pid <= (others => (others => '0'));
      dt_vw  <= (others => (others => '0'));
      vp_n   <= (others => '0');
      dt_n   <= (others => '0');
      code_r <= D_NONE;
      agr_r  <= '0'; dis_r <= '0'; msk_r <= '0';
      c_ag <= (others => '0'); c_pd <= (others => '0');
      c_vd <= (others => '0'); c_sk <= (others => '0');
      c_mk <= (others => '0'); c_up <= (others => '0');
    elsif rising_edge(clk) then
      nvp_pid := vp_pid; nvp_vw := vp_vw;
      ndt_pid := dt_pid; ndt_vw := dt_vw;
      nvp_n := vp_n; ndt_n := dt_n;
      ncode := D_NONE;
      nagr := '0'; ndis := '0'; nmsk := '0'; dif := '0';

      if eot = '1' then
        -- ---- THE DRAIN. An event only one side ever reported. ----
        --
        -- Not "skew that had not settled": the run is over. One of the two
        -- implementations saw something the other never did, which is
        -- exactly the class of finding a second implementation exists to
        -- produce.
        if nvp_n /= 0 then
          for k in 0 to QD - 2 loop
            nvp_pid(k) := nvp_pid(k+1); nvp_vw(k) := nvp_vw(k+1);
          end loop;
          nvp_n := nvp_n - 1;
          ndis := '1'; ncode := D_UNPAIRED;
        elsif ndt_n /= 0 then
          for k in 0 to QD - 2 loop
            ndt_pid(k) := ndt_pid(k+1); ndt_vw(k) := ndt_vw(k+1);
          end loop;
          ndt_n := ndt_n - 1;
          ndis := '1'; ncode := D_UNPAIRED;
        end if;
      else
        -- ---- 1. Both sides have an event: compare the OLDEST pair. ----
        if nvp_n /= 0 and ndt_n /= 0 then
          if vp_pid(0) /= dt_pid(0) then
            dif := '1'; ncode := D_PID;
          elsif vp_vw(0) /= dt_vw(0) then
            -- ---- Same PID, different INTERPRETATION. ----
            --
            -- This is the disagreement worth having a VIP for. Both sides
            -- agree on what arrived and disagree on what it MEANS -- which
            -- is always a specification reading, and is the case an adapter
            -- is most often asked to paper over.
            dif := '1'; ncode := D_VIEW;
          else
            nagr := '1';
          end if;

          if dif = '1' then
            if strict = '1' then
              ndis := '1';
            else
              -- ---- PERMISSIVE. Masked, and COUNTED. ----
              --
              -- The count is the point. A permissive run does not come back
              -- clean; it comes back with a number, and the number is how
              -- many specification arguments nobody has had yet.
              nmsk := '1';
            end if;
          end if;

          for k in 0 to QD - 2 loop
            nvp_pid(k) := nvp_pid(k+1); nvp_vw(k) := nvp_vw(k+1);
            ndt_pid(k) := ndt_pid(k+1); ndt_vw(k) := ndt_vw(k+1);
          end loop;
          nvp_n := nvp_n - 1;
          ndt_n := ndt_n - 1;
        end if;

        -- ---- 2. Enqueue whatever arrived this cycle. ----
        if vip_valid = '1' and nvp_n < to_unsigned(QD, QW+1) then
          nvp_pid(to_integer(nvp_n)) := vip_pid;
          nvp_vw(to_integer(nvp_n))  := vip_view;
          nvp_n := nvp_n + 1;
        end if;
        if dut_valid = '1' and ndt_n < to_unsigned(QD, QW+1) then
          ndt_pid(to_integer(ndt_n)) := dut_pid;
          ndt_vw(to_integer(ndt_n))  := dut_view;
          ndt_n := ndt_n + 1;
        end if;

        -- ---- 3. THE ALIGNMENT BOUND. ----
        --
        -- One side running SKEW_MAX events ahead of the other is not skew
        -- any more: alignment is lost, and every comparison after this
        -- point is between unrelated events. The queues are flushed so that
        -- the loss is reported ONCE rather than as a stream of nonsense PID
        -- disagreements.
        --
        -- Widening SKEW_MAX is the reflex and it is the wrong move: it
        -- converts a loud failure into a quiet stream of garbage.
        if nvp_n > to_unsigned(SKEW_MAX, QW+1)
           or ndt_n > to_unsigned(SKEW_MAX, QW+1) then
          nvp_n := (others => '0');
          ndt_n := (others => '0');
          ndis  := '1';
          nmsk  := '0';
          ncode := D_SKEW;
          nagr  := '0';
        end if;
      end if;

      vp_pid <= nvp_pid; vp_vw <= nvp_vw;
      dt_pid <= ndt_pid; dt_vw <= ndt_vw;
      vp_n   <= nvp_n;
      dt_n   <= ndt_n;
      code_r <= ncode;
      agr_r  <= nagr;
      dis_r  <= ndis;
      msk_r  <= nmsk;

      if nagr = '1' then c_ag <= c_ag + 1; end if;
      if nmsk = '1' then c_mk <= c_mk + 1; end if;

      -- A masked disagreement still increments its CAUSE counter. The
      -- permissive switch decides whether the run fails; it does not decide
      -- whether the disagreement happened.
      if ndis = '1' or nmsk = '1' then
        case ncode is
          when D_PID      => c_pd <= c_pd + 1;
          when D_VIEW     => c_vd <= c_vd + 1;
          when D_SKEW     => c_sk <= c_sk + 1;
          when D_UNPAIRED => c_up <= c_up + 1;
          when others     => null;
        end case;
      end if;
    end if;
  end process;

end architecture rtl;

9. Seeing Skew, Agreement, and Both Kinds of Masking

The VIP runs ahead, the pair matches, then the same disagreement in both modes

usb_vip_adapter — alignment, agreement, strict and permissive

10 cycles
A ten-cycle waveform. The VIP reports PIDs 1 and 2 before the design reports anything, so the skew rises to 2. The design then reports PID 1, the oldest pair matches and agree_pulse fires. The next pair, VIP 2 against design 9, differs and dis_pulse fires with code PID because strict is high. A later pair, VIP 3 against design 4, differs while strict is low, and masked_pulse fires instead.the VIP is 2 events aheadthe VIP is 2 events aheadsame PID and meaning: agreesame PID and meaning: agreestrict: the run failsstrict: the run failspermissive: masked AND countedpermissive: masked ANDcountedclkvip_pid1222233333dut_pid0019944444strictskew0122101000agree_pulsedis_pulsemasked_pulsedis_codeNONENONENONENONENONEPIDNONEPIDNONENONEt0t1t2t3t4t5t6t7t8t9
The VIP reports two events before the design reports its first, so the skew reaches 2. The oldest pair matches and agreement is reported. The next pair differs: in strict mode the run fails; the same difference in permissive mode is masked — and still counted, which is what stops a permissive run coming back clean.

Compare cycles 5 and 7. The disagreement is identical. dis_code reads PID in both, and n_pid_dis increments in both. The only thing strict changes is which pulse carries it — which is exactly the separation the chapter argues for: the switch decides whether the run fails, not whether the disagreement happened.

10. The Testbenches

Three exhaustive sweeps:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   1.  ALL 256 (vip_pid, dut_pid) pairs, views agreeing.
       Agreement on exactly the 16 diagonal pairs and
       disagreement on the other 240 -- which is a far
       stronger statement than "it noticed the one we
       injected".

   2.  ALL 16 (vip_view, dut_view) pairs, PIDs agreeing:
       both sides saw the same packet and read it
       differently.

   3.  Every alignment skew 0..SKEW_MAX x all 16
       combinations of {vip_valid, dut_valid, strict, eot}
       = 80 pairs.

And the phase the chapter exists for:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // strict: it fails
      b_pd = n_pid_dis; b_mk = n_masked;
      pair(4'h1, 2'd0, 4'h2, 2'd0, 1'b1);
      check(n_pid_dis == b_pd + 1, "a strict disagreement was not counted");
      check(n_masked == b_mk, "a strict disagreement was masked");

      // permissive: it is masked -- and STILL COUNTED
      b_pd = n_pid_dis; b_mk = n_masked;
      pair(4'h1, 2'd0, 4'h2, 2'd0, 1'b0);
      check(n_masked == b_mk + 1,
            "a permissive run came back clean -- masking without counting is the shim that makes a disagreement invisible, and every future disagreement of the same shape with it");
      check(n_pid_dis == b_pd + 1,
            "the permissive switch decided whether the disagreement HAPPENED, rather than whether the run fails");

The alignment bound is checked on both edges — exactly SKEW_MAX ahead is still aligned, one more is not — and the flush is checked too, because an adapter that reports the loss without flushing reports it again on every subsequent event.

10.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Testbench for usb_vip_adapter (Verilog-2005).
//
// WHAT IS EXHAUSTIVE HERE
//
//   1. ALL 256 (vip_pid, dut_pid) PAIRS, with the two views agreeing. The
//      adapter must agree on exactly the 16 diagonal pairs and disagree on
//      the other 240 -- which is a stronger statement than "it noticed the
//      disagreement we injected".
//
//   2. ALL 16 (vip_view, dut_view) pairs with the PIDs agreeing, for the
//      case that matters most: both sides saw the same packet and read it
//      differently.
//
//   3. Every alignment skew from 0 to SKEW_MAX crossed with all sixteen
//      combinations of {vip_valid, dut_valid, strict, eot} = 80 pairs.
//
// AND THE PROPERTY THE CHAPTER EXISTS FOR
//
// PERMISSIVE MODE STILL COUNTS. The same disagreement is driven twice, once
// strict and once permissive, and the checks are:
//
//     strict      dis_pulse fires        cause counter +1
//     permissive  masked_pulse fires     cause counter +1  AND  n_masked +1
//
// The cause counter increments in BOTH. The permissive switch decides
// whether the run fails; it does not decide whether the disagreement
// happened, and an adapter that forgets that is the shim this chapter is
// about.
`timescale 1ns/1ps
module tb_va_v;

  localparam integer QD       = 8;
  localparam integer QW       = 3;
  localparam integer SKEW_MAX = 4;

  localparam [2:0] D_NONE=3'd0, D_PID=3'd1, D_VIEW=3'd2, D_SKEW=3'd3,
                   D_UNPAIRED=3'd4;

  reg clk = 1'b0, rst_n = 1'b0;
  reg vip_valid = 1'b0, dut_valid = 1'b0, strict = 1'b1, eot = 1'b0;
  reg [3:0] vip_pid = 4'd0, dut_pid = 4'd0;
  reg [1:0] vip_view = 2'd0, dut_view = 2'd0;

  wire [QW:0] vip_depth, dut_depth, skew;
  wire [2:0]  dis_code;
  wire agree_pulse, dis_pulse, masked_pulse;
  wire [31:0] n_agree, n_pid_dis, n_view_dis, n_skew, n_masked, n_unpaired;

  usb_vip_adapter #(.QD(QD), .QW(QW), .SKEW_MAX(SKEW_MAX)) dut (
    .clk(clk), .rst_n(rst_n),
    .vip_valid(vip_valid), .vip_pid(vip_pid), .vip_view(vip_view),
    .dut_valid(dut_valid), .dut_pid(dut_pid), .dut_view(dut_view),
    .strict(strict), .eot(eot),
    .vip_depth(vip_depth), .dut_depth(dut_depth), .skew(skew),
    .agree_pulse(agree_pulse), .dis_pulse(dis_pulse),
    .dis_code(dis_code), .masked_pulse(masked_pulse),
    .n_agree(n_agree), .n_pid_dis(n_pid_dis), .n_view_dis(n_view_dis),
    .n_skew(n_skew), .n_masked(n_masked), .n_unpaired(n_unpaired)
  );

  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 | vd=%0d dd=%0d sk=%0d a=%b d=%b(%0d) m=%b",
                   $time, msg, vip_depth, dut_depth, skew, agree_pulse,
                   dis_pulse, dis_code, masked_pulse);
      end
    end
  endtask

  // ------------------------------------------------------------------
  // The shadow adapter. Its own queues.
  // ------------------------------------------------------------------
  reg [3:0] mvp_pid [0:QD-1];
  reg [1:0] mvp_vw  [0:QD-1];
  reg [3:0] mdt_pid [0:QD-1];
  reg [1:0] mdt_vw  [0:QD-1];
  reg [QW:0] mvp_n, mdt_n;
  reg [2:0]  m_code;
  reg        m_agr, m_dis, m_msk;
  integer    m_ag, m_pd, m_vd, m_sk, m_mk, m_up;

  integer seen [0:79];          // (SKEW_MAX+1) x 16 input combinations
  integer n_seen, n_steps;

  task model_reset;
    integer j;
    begin
      for (j = 0; j < QD; j = j + 1) begin
        mvp_pid[j] = 4'd0; mvp_vw[j] = 2'd0;
        mdt_pid[j] = 4'd0; mdt_vw[j] = 2'd0;
      end
      mvp_n = 0; mdt_n = 0; m_code = D_NONE;
      m_agr = 1'b0; m_dis = 1'b0; m_msk = 1'b0;
      m_ag = 0; m_pd = 0; m_vd = 0; m_sk = 0; m_mk = 0; m_up = 0;
      for (j = 0; j < 80; j = j + 1) seen[j] = 0;
      n_seen = 0; n_steps = 0;
    end
  endtask

  integer q, idx;
  task step(input vv, input [3:0] vp, input [1:0] vw,
            input dv, input [3:0] dp, input [1:0] dw,
            input st, input eo);
    reg [2:0] ncode;
    reg nagr, ndis, nmsk, dif;
    reg [QW:0] msk_now;
    begin
      vip_valid = vv; vip_pid = vp; vip_view = vw;
      dut_valid = dv; dut_pid = dp; dut_view = dw;
      strict = st; eot = eo;
      #1;

      check(vip_depth === mvp_n, "vip_depth disagrees with the shadow adapter");
      check(dut_depth === mdt_n, "dut_depth disagrees");
      check(skew === ((mvp_n > mdt_n) ? mvp_n : mdt_n), "skew disagrees");
      check(agree_pulse  === m_agr, "the agree pulse disagrees");
      check(dis_pulse    === m_dis, "the disagreement pulse disagrees");
      check(dis_code     === m_code, "dis_code disagrees");
      check(masked_pulse === m_msk, "the masked pulse disagrees");
      check(!(agree_pulse && dis_pulse),
            "a comparison was reported as both agreement and disagreement");
      check(!(dis_pulse && masked_pulse),
            "a disagreement was reported as both failed and masked");
      check(skew <= SKEW_MAX[QW:0] + 1,
            "the alignment queues grew past the bound -- every comparison after that point is between unrelated events");

      msk_now = (mvp_n > mdt_n) ? mvp_n : mdt_n;
      idx = msk_now * 16 + (vv ? 8 : 0) + (dv ? 4 : 0) + (st ? 2 : 0) + (eo ? 1 : 0);
      if (idx < 80) begin
        if (seen[idx] == 0) begin seen[idx] = 1; n_seen = n_seen + 1; end
      end
      n_steps = n_steps + 1;

      // ---- advance the shadow adapter ----
      ncode = D_NONE; nagr = 1'b0; ndis = 1'b0; nmsk = 1'b0; dif = 1'b0;

      if (eo) begin
        if (mvp_n != 0) begin
          for (q = 0; q < QD - 1; q = q + 1) begin
            mvp_pid[q] = mvp_pid[q+1]; mvp_vw[q] = mvp_vw[q+1];
          end
          mvp_n = mvp_n - 1'b1;
          ndis = 1'b1; ncode = D_UNPAIRED;
        end else if (mdt_n != 0) begin
          for (q = 0; q < QD - 1; q = q + 1) begin
            mdt_pid[q] = mdt_pid[q+1]; mdt_vw[q] = mdt_vw[q+1];
          end
          mdt_n = mdt_n - 1'b1;
          ndis = 1'b1; ncode = D_UNPAIRED;
        end
      end else begin
        if ((mvp_n != 0) && (mdt_n != 0)) begin
          if (mvp_pid[0] !== mdt_pid[0]) begin dif = 1'b1; ncode = D_PID; end
          else if (mvp_vw[0] !== mdt_vw[0]) begin dif = 1'b1; ncode = D_VIEW; end
          else nagr = 1'b1;
          if (dif) begin
            if (st) ndis = 1'b1; else nmsk = 1'b1;
          end
          for (q = 0; q < QD - 1; q = q + 1) begin
            mvp_pid[q] = mvp_pid[q+1]; mvp_vw[q] = mvp_vw[q+1];
            mdt_pid[q] = mdt_pid[q+1]; mdt_vw[q] = mdt_vw[q+1];
          end
          mvp_n = mvp_n - 1'b1;
          mdt_n = mdt_n - 1'b1;
        end
        if (vv && (mvp_n < QD[QW:0])) begin
          mvp_pid[mvp_n] = vp; mvp_vw[mvp_n] = vw; mvp_n = mvp_n + 1'b1;
        end
        if (dv && (mdt_n < QD[QW:0])) begin
          mdt_pid[mdt_n] = dp; mdt_vw[mdt_n] = dw; mdt_n = mdt_n + 1'b1;
        end
        if ((mvp_n > SKEW_MAX[QW:0]) || (mdt_n > SKEW_MAX[QW:0])) begin
          mvp_n = 0; mdt_n = 0;
          ndis = 1'b1; nmsk = 1'b0; ncode = D_SKEW; nagr = 1'b0;
        end
      end

      m_code = ncode; m_agr = nagr; m_dis = ndis; m_msk = nmsk;
      if (nagr) m_ag = m_ag + 1;
      if (nmsk) m_mk = m_mk + 1;
      if (ndis || nmsk) begin
        case (ncode)
          D_PID:      m_pd = m_pd + 1;
          D_VIEW:     m_vd = m_vd + 1;
          D_SKEW:     m_sk = m_sk + 1;
          D_UNPAIRED: m_up = m_up + 1;
          default: ;
        endcase
      end

      @(posedge clk); #1;
      vip_valid = 1'b0; dut_valid = 1'b0; eot = 1'b0;
    end
  endtask

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

  // Feed both sides one event each and let the comparison happen.
  task pair(input [3:0] vp, input [1:0] vw, input [3:0] dp, input [1:0] dw,
            input st);
    begin
      step(1'b1,vp,vw, 1'b1,dp,dw, st, 1'b0);
      step(1'b0,4'd0,2'd0, 1'b0,4'd0,2'd0, st, 1'b0);
    end
  endtask

  // Drain the alignment queues the way the design provides for.
  task drain;
    integer j;
    begin
      for (j = 0; j < 2*QD + 2; j = j + 1)
        step(1'b0,4'd0,2'd0, 1'b0,4'd0,2'd0, strict, 1'b1);
      check(vip_depth === 4'd0 && dut_depth === 4'd0,
            "the drain did not empty the alignment queues");
      nop(1);
    end
  endtask

  integer a, b, k, b_ag, b_pd, b_vd, b_mk, b_up, b_sk, sk2, cb;

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

    // ---- Phase A: the state after reset ----
    check(vip_depth === 4'd0 && dut_depth === 4'd0, "reset left events queued");
    check(agree_pulse === 1'b0 && dis_pulse === 1'b0,
          "reset asserted a comparison result");

    // ---- Phase B: ALL 256 PID PAIRS. Agreement on exactly the diagonal. --
    for (a = 0; a < 16; a = a + 1) begin
      for (b = 0; b < 16; b = b + 1) begin
        b_ag = n_agree; b_pd = n_pid_dis;
        pair(a[3:0], 2'd1, b[3:0], 2'd1, 1'b1);
        if (a == b) begin
          check(n_agree == b_ag + 1,
                "the two sides reported the same PID and the adapter did not agree");
          check(n_pid_dis == b_pd,
                "identical PIDs were reported as a disagreement");
        end else begin
          check(n_pid_dis == b_pd + 1,
                "the two sides decoded DIFFERENT PIDs and the adapter did not say so -- an adapter that resolves a disagreement has deleted the only finding a second implementation can produce");
          check(n_agree == b_ag,
                "a PID disagreement was counted as agreement");
        end
      end
    end

    // ---- Phase C: ALL 16 VIEW PAIRS, with the PIDs agreeing. ----
    //
    // Both sides saw the same packet and read it differently. This is the
    // disagreement worth owning a VIP for, and the one an adapter is most
    // often asked to paper over.
    for (a = 0; a < 4; a = a + 1) begin
      for (b = 0; b < 4; b = b + 1) begin
        b_ag = n_agree; b_vd = n_view_dis;
        pair(4'hC, a[1:0], 4'hC, b[1:0], 1'b1);
        if (a == b) begin
          check(n_agree == b_ag + 1, "identical views were not agreed");
          check(n_view_dis == b_vd, "identical views were reported as a disagreement");
        end else begin
          check(n_view_dis == b_vd + 1,
                "both sides saw the same packet and read it differently, and the adapter said nothing");
          check(n_agree == b_ag, "a view disagreement was counted as agreement");
        end
      end
    end

    // ---- Phase D: PERMISSIVE STILL COUNTS. ----
    for (k = 0; k < 30; k = k + 1) begin
      // strict: it fails
      b_pd = n_pid_dis; b_mk = n_masked;
      pair(4'h1, 2'd0, 4'h2, 2'd0, 1'b1);
      check(n_pid_dis == b_pd + 1, "a strict disagreement was not counted");
      check(n_masked == b_mk, "a strict disagreement was masked");

      // permissive: it is masked -- and STILL COUNTED
      b_pd = n_pid_dis; b_mk = n_masked;
      pair(4'h1, 2'd0, 4'h2, 2'd0, 1'b0);
      check(n_masked == b_mk + 1,
            "a permissive run came back clean -- masking without counting is the shim that makes a disagreement invisible, and every future disagreement of the same shape with it");
      check(n_pid_dis == b_pd + 1,
            "the permissive switch decided whether the disagreement HAPPENED, rather than whether the run fails");
    end

    // ---- Phase E: THE ALIGNMENT BOUND, both edges. ----
    drain;
    // exactly SKEW_MAX ahead: still aligned, no failure
    b_sk = n_skew;
    for (k = 0; k < SKEW_MAX; k = k + 1)
      step(1'b1, 4'h5, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
    check(skew === SKEW_MAX[QW:0],
          "the queue did not reach the alignment bound");
    check(n_skew == b_sk,
          "alignment was declared lost AT the bound rather than past it");
    // one more: alignment is lost
    step(1'b1, 4'h5, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
    nop(1);
    check(n_skew == b_sk + 1,
          "one side ran more than SKEW_MAX events ahead and the adapter carried on comparing unrelated events");
    check(skew === 4'd0,
          "the queues were not flushed after alignment was lost, so the loss will be reported again on every subsequent event");

    // ---- Phase F: UNPAIRED at end of test. ----
    drain;
    for (k = 1; k <= SKEW_MAX; k = k + 1) begin
      b_up = n_unpaired;
      for (a = 0; a < k; a = a + 1)
        step(1'b1, 4'h7, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
      drain;
      check(n_unpaired == b_up + k,
            "an event that only one side ever reported was not named at end of test -- that is exactly the class of finding a second implementation exists to produce");
    end

    // ---- Phase G: EXHAUSTIVE. Every skew x every input combination. ----
    for (sk2 = 0; sk2 <= SKEW_MAX; sk2 = sk2 + 1) begin
      for (cb = 0; cb < 16; cb = cb + 1) begin
        drain;
        for (a = 0; a < sk2; a = a + 1)
          step(1'b1, 4'h9, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
        check(skew === sk2[QW:0],
              "the sweep could not reach the skew it meant to reach");
        step(cb[3], 4'h9, 2'd0, cb[2], 4'h9, 2'd0, cb[1], cb[0]);
        step(cb[3], 4'h9, 2'd0, cb[2], 4'h9, 2'd0, cb[1], cb[0]);
      end
    end

    // ---- Phase H: random ----
    for (k = 0; k < 32000; k = k + 1)
      step(($unsigned($random) % 100) < 42, $random, $random,
           ($unsigned($random) % 100) < 42, $random, $random,
           ($unsigned($random) % 100) < 70,
           ($unsigned($random) % 1000) < 8);

    // ---- Phase I: clean agreement afterwards, so the adapter is shown to
    // ---- still work rather than merely to have stopped.
    drain;
    b_ag = n_agree;
    for (k = 0; k < 200; k = k + 1) pair(4'hE, 2'd2, 4'hE, 2'd2, 1'b1);
    check(n_agree == b_ag + 200,
          "the adapter stopped agreeing on identical views after the random phase");

    // ---- Final agreement ----
    check(n_agree    === m_ag[31:0], "n_agree disagrees with the model");
    check(n_pid_dis  === m_pd[31:0], "n_pid_dis disagrees");
    check(n_view_dis === m_vd[31:0], "n_view_dis disagrees");
    check(n_skew     === m_sk[31:0], "n_skew disagrees");
    check(n_masked   === m_mk[31:0], "n_masked disagrees");
    check(n_unpaired === m_up[31:0], "n_unpaired disagrees");

    check(n_seen == 80, "not every alignment skew was crossed with every input combination");
    check(n_agree    > 32'd0, "the two sides never agreed about anything");
    check(n_pid_dis  > 32'd0, "a PID disagreement was never seen");
    check(n_view_dis > 32'd0, "a view disagreement was never seen");
    check(n_skew     > 32'd0, "alignment was never lost");
    check(n_masked   > 32'd0, "permissive mode was never exercised");
    check(n_unpaired > 32'd0, "an unpaired event was never seen");

    $display("REACH skew-x-input=%0d/80 steps=%0d", n_seen, n_steps);
    $display("COUNTERS agree=%0d pid-dis=%0d view-dis=%0d skew=%0d masked=%0d unpaired=%0d",
             n_agree, n_pid_dis, n_view_dis, n_skew, n_masked, n_unpaired);
    $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_vip_adapter (SystemVerilog).
//
// WHAT IS EXHAUSTIVE HERE
//
//   1. ALL 256 (vip_pid, dut_pid) PAIRS, with the two views agreeing. The
//      adapter must agree on exactly the 16 diagonal pairs and disagree on
//      the other 240 -- which is a stronger statement than "it noticed the
//      disagreement we injected".
//
//   2. ALL 16 (vip_view, dut_view) pairs with the PIDs agreeing, for the
//      case that matters most: both sides saw the same packet and read it
//      differently.
//
//   3. Every alignment skew from 0 to SKEW_MAX crossed with all sixteen
//      combinations of {vip_valid, dut_valid, strict, eot} = 80 pairs.
//
// AND THE PROPERTY THE CHAPTER EXISTS FOR
//
// PERMISSIVE MODE STILL COUNTS. The same disagreement is driven twice, once
// strict and once permissive, and the checks are:
//
//     strict      dis_pulse fires        cause counter +1
//     permissive  masked_pulse fires     cause counter +1  AND  n_masked +1
//
// The cause counter increments in BOTH. The permissive switch decides
// whether the run fails; it does not decide whether the disagreement
// happened, and an adapter that forgets that is the shim this chapter is
// about.
`timescale 1ns/1ps
module tb_va_sv;
  import usb_vip_pkg::*;

  localparam int QD       = 8;
  localparam int QW       = 3;
  localparam int SKEW_MAX = 4;

  logic clk = 1'b0, rst_n = 1'b0;
  logic vip_valid = 1'b0, dut_valid = 1'b0, strict = 1'b1, eot = 1'b0;
  logic [3:0] vip_pid = '0, dut_pid = '0;
  logic [1:0] vip_view = '0, dut_view = '0;

  logic [QW:0] vip_depth, dut_depth, skew;
  dis_e        dis_code;
  logic agree_pulse, dis_pulse, masked_pulse;
  logic [31:0] n_agree, n_pid_dis, n_view_dis, n_skew, n_masked, n_unpaired;

  usb_vip_adapter #(.QD(QD), .QW(QW), .SKEW_MAX(SKEW_MAX)) 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 | vd=%0d dd=%0d sk=%0d a=%b d=%b(%0d) m=%b",
                   $time, msg, vip_depth, dut_depth, skew, agree_pulse,
                   dis_pulse, dis_code, masked_pulse);
      end
    end
  endtask

  // ------------------------------------------------------------------
  // The shadow adapter. Its own queues.
  // ------------------------------------------------------------------
  logic [3:0] mvp_pid [QD];
  logic [1:0] mvp_vw  [QD];
  logic [3:0] mdt_pid [QD];
  logic [1:0] mdt_vw  [QD];
  logic [QW:0] mvp_n, mdt_n;
  dis_e        m_code;
  logic        m_agr, m_dis, m_msk;
  int          m_ag, m_pd, m_vd, m_sk, m_mk, m_up;

  int seen [80];                // (SKEW_MAX+1) x 16 input combinations
  int n_seen, n_steps;

  task automatic model_reset();
    int j;
    begin
      for (j = 0; j < QD; j++) begin
        mvp_pid[j] = 4'd0; mvp_vw[j] = 2'd0;
        mdt_pid[j] = 4'd0; mdt_vw[j] = 2'd0;
      end
      mvp_n = 0; mdt_n = 0; m_code = D_NONE;
      m_agr = 1'b0; m_dis = 1'b0; m_msk = 1'b0;
      m_ag = 0; m_pd = 0; m_vd = 0; m_sk = 0; m_mk = 0; m_up = 0;
      for (j = 0; j < 80; j++) seen[j] = 0;
      n_seen = 0; n_steps = 0;
    end
  endtask

  int q, idx;
  task automatic step(input logic vv, input logic [3:0] vp, input logic [1:0] vw,
                      input logic dv, input logic [3:0] dp, input logic [1:0] dw,
                      input logic st, input logic eo);
    dis_e ncode;
    logic nagr, ndis, nmsk, dif;
    logic [QW:0] msk_now;
    begin
      vip_valid = vv; vip_pid = vp; vip_view = vw;
      dut_valid = dv; dut_pid = dp; dut_view = dw;
      strict = st; eot = eo;
      #1;

      check(vip_depth === mvp_n, "vip_depth disagrees with the shadow adapter");
      check(dut_depth === mdt_n, "dut_depth disagrees");
      check(skew === ((mvp_n > mdt_n) ? mvp_n : mdt_n), "skew disagrees");
      check(agree_pulse  === m_agr, "the agree pulse disagrees");
      check(dis_pulse    === m_dis, "the disagreement pulse disagrees");
      check(dis_code     === m_code, "dis_code disagrees");
      check(masked_pulse === m_msk, "the masked pulse disagrees");
      check(!(agree_pulse && dis_pulse),
            "a comparison was reported as both agreement and disagreement");
      check(!(dis_pulse && masked_pulse),
            "a disagreement was reported as both failed and masked");
      check(skew <= (QW+1)'(SKEW_MAX) + 1,
            "the alignment queues grew past the bound -- every comparison after that point is between unrelated events");

      msk_now = (mvp_n > mdt_n) ? mvp_n : mdt_n;
      idx = int'(msk_now) * 16 + (vv ? 8 : 0) + (dv ? 4 : 0) + (st ? 2 : 0) + (eo ? 1 : 0);
      if (idx < 80) begin
        if (seen[idx] == 0) begin seen[idx] = 1; n_seen = n_seen + 1; end
      end
      n_steps++;

      // ---- advance the shadow adapter ----
      ncode = D_NONE; nagr = 1'b0; ndis = 1'b0; nmsk = 1'b0; dif = 1'b0;

      if (eo) begin
        if (mvp_n != 0) begin
          for (q = 0; q < QD - 1; q++) begin
            mvp_pid[q] = mvp_pid[q+1]; mvp_vw[q] = mvp_vw[q+1];
          end
          mvp_n = mvp_n - 1'b1;
          ndis = 1'b1; ncode = D_UNPAIRED;
        end else if (mdt_n != 0) begin
          for (q = 0; q < QD - 1; q++) begin
            mdt_pid[q] = mdt_pid[q+1]; mdt_vw[q] = mdt_vw[q+1];
          end
          mdt_n = mdt_n - 1'b1;
          ndis = 1'b1; ncode = D_UNPAIRED;
        end
      end else begin
        if ((mvp_n != 0) && (mdt_n != 0)) begin
          if (mvp_pid[0] !== mdt_pid[0]) begin dif = 1'b1; ncode = D_PID; end
          else if (mvp_vw[0] !== mdt_vw[0]) begin dif = 1'b1; ncode = D_VIEW; end
          else nagr = 1'b1;
          if (dif) begin
            if (st) ndis = 1'b1; else nmsk = 1'b1;
          end
          for (q = 0; q < QD - 1; q++) begin
            mvp_pid[q] = mvp_pid[q+1]; mvp_vw[q] = mvp_vw[q+1];
            mdt_pid[q] = mdt_pid[q+1]; mdt_vw[q] = mdt_vw[q+1];
          end
          mvp_n = mvp_n - 1'b1;
          mdt_n = mdt_n - 1'b1;
        end
        if (vv && (mvp_n < (QW+1)'(QD))) begin
          mvp_pid[mvp_n] = vp; mvp_vw[mvp_n] = vw; mvp_n = mvp_n + 1'b1;
        end
        if (dv && (mdt_n < (QW+1)'(QD))) begin
          mdt_pid[mdt_n] = dp; mdt_vw[mdt_n] = dw; mdt_n = mdt_n + 1'b1;
        end
        if ((mvp_n > (QW+1)'(SKEW_MAX)) || (mdt_n > (QW+1)'(SKEW_MAX))) begin
          mvp_n = 0; mdt_n = 0;
          ndis = 1'b1; nmsk = 1'b0; ncode = D_SKEW; nagr = 1'b0;
        end
      end

      m_code = ncode; m_agr = nagr; m_dis = ndis; m_msk = nmsk;
      if (nagr) m_ag = m_ag + 1;
      if (nmsk) m_mk = m_mk + 1;
      if (ndis || nmsk) begin
        case (ncode)
          D_PID:      m_pd = m_pd + 1;
          D_VIEW:     m_vd = m_vd + 1;
          D_SKEW:     m_sk = m_sk + 1;
          D_UNPAIRED: m_up = m_up + 1;
          default: ;
        endcase
      end

      @(posedge clk); #1;
      vip_valid = 1'b0; dut_valid = 1'b0; eot = 1'b0;
    end
  endtask

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

  // Feed both sides one event each and let the comparison happen.
  task automatic pair(input logic [3:0] vp, input logic [1:0] vw,
                      input logic [3:0] dp, input logic [1:0] dw,
                      input logic st);
    begin
      step(1'b1,vp,vw, 1'b1,dp,dw, st, 1'b0);
      step(1'b0,4'd0,2'd0, 1'b0,4'd0,2'd0, st, 1'b0);
    end
  endtask

  // Drain the alignment queues the way the design provides for.
  task automatic drain();
    repeat (2*QD + 2) step(1'b0,4'd0,2'd0, 1'b0,4'd0,2'd0, strict, 1'b1);
    check(vip_depth === '0 && dut_depth === '0,
          "the drain did not empty the alignment queues");
    nop(1);
  endtask

  int a, b, k, b_ag, b_pd, b_vd, b_mk, b_up, b_sk, sk2, cb;

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

    // ---- Phase A: the state after reset ----
    check(vip_depth === 4'd0 && dut_depth === 4'd0, "reset left events queued");
    check(agree_pulse === 1'b0 && dis_pulse === 1'b0,
          "reset asserted a comparison result");

    // ---- Phase B: ALL 256 PID PAIRS. Agreement on exactly the diagonal. --
    for (a = 0; a < 16; a++) begin
      for (b = 0; b < 16; b++) begin
        b_ag = n_agree; b_pd = n_pid_dis;
        pair(4'(a), 2'd1, 4'(b), 2'd1, 1'b1);
        if (a == b) begin
          check(n_agree == b_ag + 1,
                "the two sides reported the same PID and the adapter did not agree");
          check(n_pid_dis == b_pd,
                "identical PIDs were reported as a disagreement");
        end else begin
          check(n_pid_dis == b_pd + 1,
                "the two sides decoded DIFFERENT PIDs and the adapter did not say so -- an adapter that resolves a disagreement has deleted the only finding a second implementation can produce");
          check(n_agree == b_ag,
                "a PID disagreement was counted as agreement");
        end
      end
    end

    // ---- Phase C: ALL 16 VIEW PAIRS, with the PIDs agreeing. ----
    //
    // Both sides saw the same packet and read it differently. This is the
    // disagreement worth owning a VIP for, and the one an adapter is most
    // often asked to paper over.
    for (a = 0; a < 4; a++) begin
      for (b = 0; b < 4; b++) begin
        b_ag = n_agree; b_vd = n_view_dis;
        pair(4'hC, 2'(a), 4'hC, 2'(b), 1'b1);
        if (a == b) begin
          check(n_agree == b_ag + 1, "identical views were not agreed");
          check(n_view_dis == b_vd, "identical views were reported as a disagreement");
        end else begin
          check(n_view_dis == b_vd + 1,
                "both sides saw the same packet and read it differently, and the adapter said nothing");
          check(n_agree == b_ag, "a view disagreement was counted as agreement");
        end
      end
    end

    // ---- Phase D: PERMISSIVE STILL COUNTS. ----
    for (k = 0; k < 30; k++) begin
      // strict: it fails
      b_pd = n_pid_dis; b_mk = n_masked;
      pair(4'h1, 2'd0, 4'h2, 2'd0, 1'b1);
      check(n_pid_dis == b_pd + 1, "a strict disagreement was not counted");
      check(n_masked == b_mk, "a strict disagreement was masked");

      // permissive: it is masked -- and STILL COUNTED
      b_pd = n_pid_dis; b_mk = n_masked;
      pair(4'h1, 2'd0, 4'h2, 2'd0, 1'b0);
      check(n_masked == b_mk + 1,
            "a permissive run came back clean -- masking without counting is the shim that makes a disagreement invisible, and every future disagreement of the same shape with it");
      check(n_pid_dis == b_pd + 1,
            "the permissive switch decided whether the disagreement HAPPENED, rather than whether the run fails");
    end

    // ---- Phase E: THE ALIGNMENT BOUND, both edges. ----
    drain();
    // exactly SKEW_MAX ahead: still aligned, no failure
    b_sk = n_skew;
    for (k = 0; k < SKEW_MAX; k++)
      step(1'b1, 4'h5, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
    check(skew === (QW+1)'(SKEW_MAX),
          "the queue did not reach the alignment bound");
    check(n_skew == b_sk,
          "alignment was declared lost AT the bound rather than past it");
    // one more: alignment is lost
    step(1'b1, 4'h5, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
    nop(1);
    check(n_skew == b_sk + 1,
          "one side ran more than SKEW_MAX events ahead and the adapter carried on comparing unrelated events");
    check(skew === '0,
          "the queues were not flushed after alignment was lost, so the loss will be reported again on every subsequent event");

    // ---- Phase F: UNPAIRED at end of test. ----
    drain();
    for (k = 1; k <= SKEW_MAX; k++) begin
      b_up = n_unpaired;
      for (a = 0; a < k; a++)
        step(1'b1, 4'h7, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
      drain();
      check(n_unpaired == b_up + k,
            "an event that only one side ever reported was not named at end of test -- that is exactly the class of finding a second implementation exists to produce");
    end

    // ---- Phase G: EXHAUSTIVE. Every skew x every input combination. ----
    for (sk2 = 0; sk2 <= SKEW_MAX; sk2++) begin
      for (cb = 0; cb < 16; cb++) begin
        drain();
        for (a = 0; a < sk2; a++)
          step(1'b1, 4'h9, 2'd0, 1'b0, 4'd0, 2'd0, 1'b1, 1'b0);
        check(skew === (QW+1)'(sk2),
              "the sweep could not reach the skew it meant to reach");
        step(1'(cb[3]), 4'h9, 2'd0, 1'(cb[2]), 4'h9, 2'd0, 1'(cb[1]), 1'(cb[0]));
        step(1'(cb[3]), 4'h9, 2'd0, 1'(cb[2]), 4'h9, 2'd0, 1'(cb[1]), 1'(cb[0]));
      end
    end

    // ---- Phase H: random ----
    for (k = 0; k < 32000; k++)
      step($urandom_range(0,99) < 42, 4'($urandom()), 2'($urandom()),
           $urandom_range(0,99) < 42, 4'($urandom()), 2'($urandom()),
           $urandom_range(0,99) < 70,
           $urandom_range(0,999) < 8);

    // ---- Phase I: clean agreement afterwards, so the adapter is shown to
    // ---- still work rather than merely to have stopped.
    drain();
    b_ag = n_agree;
    for (k = 0; k < 200; k++) pair(4'hE, 2'd2, 4'hE, 2'd2, 1'b1);
    check(n_agree == b_ag + 200,
          "the adapter stopped agreeing on identical views after the random phase");

    // ---- Final agreement ----
    check(n_agree    === 32'(m_ag), "n_agree disagrees with the model");
    check(n_pid_dis  === 32'(m_pd), "n_pid_dis disagrees");
    check(n_view_dis === 32'(m_vd), "n_view_dis disagrees");
    check(n_skew     === 32'(m_sk), "n_skew disagrees");
    check(n_masked   === 32'(m_mk), "n_masked disagrees");
    check(n_unpaired === 32'(m_up), "n_unpaired disagrees");

    check(n_seen == 80, "not every alignment skew was crossed with every input combination");
    check(n_agree    > 32'd0, "the two sides never agreed about anything");
    check(n_pid_dis  > 32'd0, "a PID disagreement was never seen");
    check(n_view_dis > 32'd0, "a view disagreement was never seen");
    check(n_skew     > 32'd0, "alignment was never lost");
    check(n_masked   > 32'd0, "permissive mode was never exercised");
    check(n_unpaired > 32'd0, "an unpaired event was never seen");

    $display("REACH skew-x-input=%0d/80 steps=%0d", n_seen, n_steps);
    $display("COUNTERS agree=%0d pid-dis=%0d view-dis=%0d skew=%0d masked=%0d unpaired=%0d",
             n_agree, n_pid_dis, n_view_dis, n_skew, n_masked, n_unpaired);
    $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_vip_adapter (VHDL-2008).
--
-- WHAT IS EXHAUSTIVE HERE
--
--   1. ALL 256 (vip_pid, dut_pid) PAIRS, with the two views agreeing. The
--      adapter must agree on exactly the 16 diagonal pairs and disagree on
--      the other 240 -- which is a stronger statement than "it noticed the
--      disagreement we injected".
--
--   2. ALL 16 (vip_view, dut_view) pairs with the PIDs agreeing, for the
--      case that matters most: both sides saw the same packet and read it
--      differently.
--
--   3. Every alignment skew from 0 to SKEW_MAX crossed with all sixteen
--      combinations of (vip_valid, dut_valid, strict, eot) = 80 pairs.
--
-- AND THE PROPERTY THE CHAPTER EXISTS FOR
--
-- PERMISSIVE MODE STILL COUNTS. The same disagreement is driven twice, once
-- strict and once permissive, and the checks are:
--
--     strict      dis_pulse fires        cause counter +1
--     permissive  masked_pulse fires     cause counter +1  AND  n_masked +1
--
-- The cause counter increments in BOTH. The permissive switch decides
-- whether the run fails; it does not decide whether the disagreement
-- happened, and an adapter that forgets that is the shim this chapter is
-- about.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.usb_vip_pkg.all;

entity tb_va_vhdl is
end entity tb_va_vhdl;

architecture sim of tb_va_vhdl is

  constant QD       : integer := 8;
  constant QW       : integer := 3;
  constant SKEW_MAX : integer := 4;

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

  signal vip_valid, dut_valid, eot : std_logic := '0';
  signal strict : std_logic := '1';
  signal vip_pid, dut_pid : std_logic_vector(3 downto 0) := (others => '0');
  signal vip_view, dut_view : std_logic_vector(1 downto 0) := (others => '0');

  signal vip_depth, dut_depth, skew : std_logic_vector(QW downto 0);
  signal dis_code : std_logic_vector(2 downto 0);
  signal agree_pulse, dis_pulse, masked_pulse : std_logic;
  signal n_agree, n_pid_dis, n_view_dis : std_logic_vector(31 downto 0);
  signal n_skew, n_masked, n_unpaired   : std_logic_vector(31 downto 0);

begin

  dut : entity work.usb_vip_adapter
    generic map (QD => QD, QW => QW, SKEW_MAX => SKEW_MAX)
    port map (
      clk => clk, rst_n => rst_n,
      vip_valid => vip_valid, vip_pid => vip_pid, vip_view => vip_view,
      dut_valid => dut_valid, dut_pid => dut_pid, dut_view => dut_view,
      strict => strict, eot => eot,
      vip_depth => vip_depth, dut_depth => dut_depth, skew => skew,
      agree_pulse => agree_pulse, dis_pulse => dis_pulse,
      dis_code => dis_code, masked_pulse => masked_pulse,
      n_agree => n_agree, n_pid_dis => n_pid_dis, n_view_dis => n_view_dis,
      n_skew => n_skew, n_masked => n_masked, n_unpaired => n_unpaired
    );

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

  stim : process
    type pid_arr is array (0 to QD-1) of std_logic_vector(3 downto 0);
    type vw_arr  is array (0 to QD-1) of std_logic_vector(1 downto 0);
    type seen_arr is array (0 to 79) of integer;

    variable errors, checks : integer := 0;

    -- ---- The shadow adapter. Its own queues. ----
    variable mvp_pid, mdt_pid : pid_arr := (others => (others => '0'));
    variable mvp_vw,  mdt_vw  : vw_arr  := (others => (others => '0'));
    variable mvp_n, mdt_n : unsigned(QW downto 0) := (others => '0');
    variable m_code : dis_t := D_NONE;
    variable m_agr, m_dis, m_msk : std_logic := '0';
    variable m_ag, m_pd, m_vd, m_sk, m_mk, m_up : integer := 0;

    variable seen : seen_arr := (others => 0);
    variable n_seen, n_steps : integer := 0;

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

    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_slv (w : integer) return std_logic_vector is
      variable u : unsigned(31 downto 0);
    begin
      u := rnd32;
      return std_logic_vector(u(w-1 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;

    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 &
                 " | vd=" & integer'image(to_integer(mvp_n)) &
                 " dd=" & integer'image(to_integer(mdt_n)) &
                 " code=" & integer'image(dis_t'pos(m_code))
            severity note;
        end if;
      end if;
    end procedure;

    procedure step (vv : std_logic; vp : std_logic_vector(3 downto 0);
                    vw : std_logic_vector(1 downto 0);
                    dv : std_logic; dp : std_logic_vector(3 downto 0);
                    dw : std_logic_vector(1 downto 0);
                    st, eo : std_logic) is
      variable ncode : dis_t;
      variable nagr, ndis, nmsk, dif : std_logic;
      variable msk_now : unsigned(QW downto 0);
      variable idx : integer;
    begin
      vip_valid <= vv; vip_pid <= vp; vip_view <= vw;
      dut_valid <= dv; dut_pid <= dp; dut_view <= dw;
      strict <= st; eot <= eo;
      wait for 1 ns;

      chk(unsigned(vip_depth) = mvp_n, "vip_depth disagrees with the shadow adapter");
      chk(unsigned(dut_depth) = mdt_n, "dut_depth disagrees");
      if mvp_n > mdt_n then
        chk(unsigned(skew) = mvp_n, "skew disagrees");
      else
        chk(unsigned(skew) = mdt_n, "skew disagrees");
      end if;
      chk(agree_pulse  = m_agr, "the agree pulse disagrees");
      chk(dis_pulse    = m_dis, "the disagreement pulse disagrees");
      chk(dis_code     = d_code(m_code), "dis_code disagrees");
      chk(masked_pulse = m_msk, "the masked pulse disagrees");
      chk(not (agree_pulse = '1' and dis_pulse = '1'),
          "a comparison was reported as both agreement and disagreement");
      chk(not (dis_pulse = '1' and masked_pulse = '1'),
          "a disagreement was reported as both failed and masked");
      chk(unsigned(skew) <= to_unsigned(SKEW_MAX + 1, QW+1),
          "the alignment queues grew past the bound -- every comparison after that point is between unrelated events");

      if mvp_n > mdt_n then msk_now := mvp_n; else msk_now := mdt_n; end if;
      idx := to_integer(msk_now) * 16;
      if vv = '1' then idx := idx + 8; end if;
      if dv = '1' then idx := idx + 4; end if;
      if st = '1' then idx := idx + 2; end if;
      if eo = '1' then idx := idx + 1; end if;
      if idx < 80 then
        if seen(idx) = 0 then seen(idx) := 1; n_seen := n_seen + 1; end if;
      end if;
      n_steps := n_steps + 1;

      -- ---- advance the shadow adapter ----
      ncode := D_NONE; nagr := '0'; ndis := '0'; nmsk := '0'; dif := '0';

      if eo = '1' then
        if mvp_n /= 0 then
          for k in 0 to QD - 2 loop
            mvp_pid(k) := mvp_pid(k+1); mvp_vw(k) := mvp_vw(k+1);
          end loop;
          mvp_n := mvp_n - 1;
          ndis := '1'; ncode := D_UNPAIRED;
        elsif mdt_n /= 0 then
          for k in 0 to QD - 2 loop
            mdt_pid(k) := mdt_pid(k+1); mdt_vw(k) := mdt_vw(k+1);
          end loop;
          mdt_n := mdt_n - 1;
          ndis := '1'; ncode := D_UNPAIRED;
        end if;
      else
        if mvp_n /= 0 and mdt_n /= 0 then
          if mvp_pid(0) /= mdt_pid(0) then
            dif := '1'; ncode := D_PID;
          elsif mvp_vw(0) /= mdt_vw(0) then
            dif := '1'; ncode := D_VIEW;
          else
            nagr := '1';
          end if;
          if dif = '1' then
            if st = '1' then ndis := '1'; else nmsk := '1'; end if;
          end if;
          for k in 0 to QD - 2 loop
            mvp_pid(k) := mvp_pid(k+1); mvp_vw(k) := mvp_vw(k+1);
            mdt_pid(k) := mdt_pid(k+1); mdt_vw(k) := mdt_vw(k+1);
          end loop;
          mvp_n := mvp_n - 1;
          mdt_n := mdt_n - 1;
        end if;
        if vv = '1' and mvp_n < to_unsigned(QD, QW+1) then
          mvp_pid(to_integer(mvp_n)) := vp;
          mvp_vw(to_integer(mvp_n))  := vw;
          mvp_n := mvp_n + 1;
        end if;
        if dv = '1' and mdt_n < to_unsigned(QD, QW+1) then
          mdt_pid(to_integer(mdt_n)) := dp;
          mdt_vw(to_integer(mdt_n))  := dw;
          mdt_n := mdt_n + 1;
        end if;
        if mvp_n > to_unsigned(SKEW_MAX, QW+1)
           or mdt_n > to_unsigned(SKEW_MAX, QW+1) then
          mvp_n := (others => '0');
          mdt_n := (others => '0');
          ndis := '1'; nmsk := '0'; ncode := D_SKEW; nagr := '0';
        end if;
      end if;

      m_code := ncode; m_agr := nagr; m_dis := ndis; m_msk := nmsk;
      if nagr = '1' then m_ag := m_ag + 1; end if;
      if nmsk = '1' then m_mk := m_mk + 1; end if;
      if ndis = '1' or nmsk = '1' then
        case ncode is
          when D_PID      => m_pd := m_pd + 1;
          when D_VIEW     => m_vd := m_vd + 1;
          when D_SKEW     => m_sk := m_sk + 1;
          when D_UNPAIRED => m_up := m_up + 1;
          when others     => null;
        end case;
      end if;

      wait until rising_edge(clk);
      wait for 1 ns;
      vip_valid <= '0'; dut_valid <= '0'; eot <= '0';
    end procedure;

    constant Z4 : std_logic_vector(3 downto 0) := (others => '0');
    constant Z2 : std_logic_vector(1 downto 0) := (others => '0');

    procedure nop (n : integer) is
    begin
      for j in 1 to n loop
        step('0',Z4,Z2, '0',Z4,Z2, strict, '0');
      end loop;
    end procedure;

    -- Feed both sides one event each and let the comparison happen.
    procedure pair (vp : std_logic_vector(3 downto 0);
                    vw : std_logic_vector(1 downto 0);
                    dp : std_logic_vector(3 downto 0);
                    dw : std_logic_vector(1 downto 0);
                    st : std_logic) is
    begin
      step('1',vp,vw, '1',dp,dw, st, '0');
      step('0',Z4,Z2, '0',Z4,Z2, st, '0');
    end procedure;

    -- Drain the alignment queues the way the design provides for.
    procedure drain is
    begin
      for j in 1 to 2*QD + 2 loop
        step('0',Z4,Z2, '0',Z4,Z2, strict, '1');
      end loop;
      chk(unsigned(vip_depth) = 0 and unsigned(dut_depth) = 0,
          "the drain did not empty the alignment queues");
      nop(1);
    end procedure;

    function p4 (n : integer) return std_logic_vector is
    begin
      return std_logic_vector(to_unsigned(n, 4));
    end function;

    function p2 (n : integer) return std_logic_vector is
    begin
      return std_logic_vector(to_unsigned(n, 2));
    end function;

    variable b_ag, b_pd, b_vd, b_mk, b_up, b_sk : integer := 0;
    variable vv_v, dv_v, st_v, eo_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(unsigned(vip_depth) = 0 and unsigned(dut_depth) = 0,
        "reset left events queued");
    chk(agree_pulse = '0' and dis_pulse = '0',
        "reset asserted a comparison result");

    -- ---- Phase B: ALL 256 PID PAIRS. Agreement on exactly the diagonal. --
    for a in 0 to 15 loop
      for b in 0 to 15 loop
        b_ag := to_integer(unsigned(n_agree));
        b_pd := to_integer(unsigned(n_pid_dis));
        pair(p4(a), "01", p4(b), "01", '1');
        if a = b then
          chk(to_integer(unsigned(n_agree)) = b_ag + 1,
              "the two sides reported the same PID and the adapter did not agree");
          chk(to_integer(unsigned(n_pid_dis)) = b_pd,
              "identical PIDs were reported as a disagreement");
        else
          chk(to_integer(unsigned(n_pid_dis)) = b_pd + 1,
              "the two sides decoded DIFFERENT PIDs and the adapter did not say so -- an adapter that resolves a disagreement has deleted the only finding a second implementation can produce");
          chk(to_integer(unsigned(n_agree)) = b_ag,
              "a PID disagreement was counted as agreement");
        end if;
      end loop;
    end loop;

    -- ---- Phase C: ALL 16 VIEW PAIRS, with the PIDs agreeing. ----
    for a in 0 to 3 loop
      for b in 0 to 3 loop
        b_ag := to_integer(unsigned(n_agree));
        b_vd := to_integer(unsigned(n_view_dis));
        pair(x"C", p2(a), x"C", p2(b), '1');
        if a = b then
          chk(to_integer(unsigned(n_agree)) = b_ag + 1,
              "identical views were not agreed");
          chk(to_integer(unsigned(n_view_dis)) = b_vd,
              "identical views were reported as a disagreement");
        else
          chk(to_integer(unsigned(n_view_dis)) = b_vd + 1,
              "both sides saw the same packet and read it differently, and the adapter said nothing");
          chk(to_integer(unsigned(n_agree)) = b_ag,
              "a view disagreement was counted as agreement");
        end if;
      end loop;
    end loop;

    -- ---- Phase D: PERMISSIVE STILL COUNTS. ----
    for k in 0 to 29 loop
      b_pd := to_integer(unsigned(n_pid_dis));
      b_mk := to_integer(unsigned(n_masked));
      pair(x"1", "00", x"2", "00", '1');
      chk(to_integer(unsigned(n_pid_dis)) = b_pd + 1,
          "a strict disagreement was not counted");
      chk(to_integer(unsigned(n_masked)) = b_mk,
          "a strict disagreement was masked");

      b_pd := to_integer(unsigned(n_pid_dis));
      b_mk := to_integer(unsigned(n_masked));
      pair(x"1", "00", x"2", "00", '0');
      chk(to_integer(unsigned(n_masked)) = b_mk + 1,
          "a permissive run came back clean -- masking without counting is the shim that makes a disagreement invisible, and every future disagreement of the same shape with it");
      chk(to_integer(unsigned(n_pid_dis)) = b_pd + 1,
          "the permissive switch decided whether the disagreement HAPPENED, rather than whether the run fails");
    end loop;

    -- ---- Phase E: THE ALIGNMENT BOUND, both edges. ----
    drain;
    b_sk := to_integer(unsigned(n_skew));
    for k in 1 to SKEW_MAX loop
      step('1', x"5", "00", '0', Z4, Z2, '1', '0');
    end loop;
    chk(unsigned(skew) = to_unsigned(SKEW_MAX, QW+1),
        "the queue did not reach the alignment bound");
    chk(to_integer(unsigned(n_skew)) = b_sk,
        "alignment was declared lost AT the bound rather than past it");
    step('1', x"5", "00", '0', Z4, Z2, '1', '0');
    nop(1);
    chk(to_integer(unsigned(n_skew)) = b_sk + 1,
        "one side ran more than SKEW_MAX events ahead and the adapter carried on comparing unrelated events");
    chk(unsigned(skew) = 0,
        "the queues were not flushed after alignment was lost, so the loss will be reported again on every subsequent event");

    -- ---- Phase F: UNPAIRED at end of test. ----
    drain;
    for k in 1 to SKEW_MAX loop
      b_up := to_integer(unsigned(n_unpaired));
      for a in 1 to k loop
        step('1', x"7", "00", '0', Z4, Z2, '1', '0');
      end loop;
      drain;
      chk(to_integer(unsigned(n_unpaired)) = b_up + k,
          "an event that only one side ever reported was not named at end of test -- that is exactly the class of finding a second implementation exists to produce");
    end loop;

    -- ---- Phase G: EXHAUSTIVE. Every skew x every input combination. ----
    for sk2 in 0 to SKEW_MAX loop
      for cb in 0 to 15 loop
        drain;
        for a in 1 to sk2 loop
          step('1', x"9", "00", '0', Z4, Z2, '1', '0');
        end loop;
        chk(unsigned(skew) = to_unsigned(sk2, QW+1),
            "the sweep could not reach the skew it meant to reach");
        if (cb / 8) mod 2 = 1 then vv_v := '1'; else vv_v := '0'; end if;
        if (cb / 4) mod 2 = 1 then dv_v := '1'; else dv_v := '0'; end if;
        if (cb / 2) mod 2 = 1 then st_v := '1'; else st_v := '0'; end if;
        if  cb      mod 2 = 1 then eo_v := '1'; else eo_v := '0'; end if;
        step(vv_v, x"9", "00", dv_v, x"9", "00", st_v, eo_v);
        step(vv_v, x"9", "00", dv_v, x"9", "00", st_v, eo_v);
      end loop;
    end loop;

    -- ---- Phase H: random ----
    for k in 0 to 31999 loop
      vv_v := rnd_lt(42, 100);
      dv_v := rnd_lt(42, 100);
      st_v := rnd_lt(70, 100);
      eo_v := rnd_lt(8, 1000);
      step(vv_v, rnd_slv(4), rnd_slv(2), dv_v, rnd_slv(4), rnd_slv(2), st_v, eo_v);
    end loop;

    -- ---- Phase I: clean agreement afterwards. ----
    drain;
    b_ag := to_integer(unsigned(n_agree));
    for k in 1 to 200 loop
      pair(x"E", "10", x"E", "10", '1');
    end loop;
    chk(to_integer(unsigned(n_agree)) = b_ag + 200,
        "the adapter stopped agreeing on identical views after the random phase");

    -- ---- Final agreement ----
    chk(to_integer(unsigned(n_agree))    = m_ag, "n_agree disagrees with the model");
    chk(to_integer(unsigned(n_pid_dis))  = m_pd, "n_pid_dis disagrees");
    chk(to_integer(unsigned(n_view_dis)) = m_vd, "n_view_dis disagrees");
    chk(to_integer(unsigned(n_skew))     = m_sk, "n_skew disagrees");
    chk(to_integer(unsigned(n_masked))   = m_mk, "n_masked disagrees");
    chk(to_integer(unsigned(n_unpaired)) = m_up, "n_unpaired disagrees");

    chk(n_seen = 80, "not every alignment skew was crossed with every input combination");
    chk(to_integer(unsigned(n_agree))    > 0, "the two sides never agreed about anything");
    chk(to_integer(unsigned(n_pid_dis))  > 0, "a PID disagreement was never seen");
    chk(to_integer(unsigned(n_view_dis)) > 0, "a view disagreement was never seen");
    chk(to_integer(unsigned(n_skew))     > 0, "alignment was never lost");
    chk(to_integer(unsigned(n_masked))   > 0, "permissive mode was never exercised");
    chk(to_integer(unsigned(n_unpaired)) > 0, "an unpaired event was never seen");

    write(ln, string'("REACH skew-x-input=") & integer'image(n_seen) &
              "/80 steps=" & integer'image(n_steps));
    writeline(output, ln);
    write(ln, string'("COUNTERS agree=") & integer'image(to_integer(unsigned(n_agree))) &
              " pid-dis=" & integer'image(to_integer(unsigned(n_pid_dis))) &
              " view-dis=" & integer'image(to_integer(unsigned(n_view_dis))) &
              " skew=" & integer'image(to_integer(unsigned(n_skew))) &
              " masked=" & integer'image(to_integer(unsigned(n_masked))) &
              " unpaired=" & integer'image(to_integer(unsigned(n_unpaired))));
    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
skew x input80 / 8080 / 8080 / 80
PID pairs compared256 / 256256 / 256256 / 256
view pairs compared16 / 1616 / 1616 / 16
Steps350533505335053
Checks executed351385351385351385
agreements397433583
PID disagreements109241101711025
view disagreements533536369
alignment losses694711657
masked (permissive)336433803331
unpaired at drain417412411
ResultPASSPASSPASS

The 256-pair sweep is the row worth dwelling on. Agreement on exactly the diagonal is a much stronger claim than "the injected disagreement was caught", because it also rules out an adapter that agrees too readily — which is the mutation this chapter is about.

12. Mutation Testing

#MutationVerilogSysVerVHDL
X6the compared pair is not removed from the queues111865112406113217
X4the alignment bound is removed881289417293650
X3a PID disagreement is reported as agreement172811751817166
X2permissive masks WITHOUT counting342834443395
X1only PIDs are compared — "read differently" is invisible162716361135
X7end of test discards what only one side saw840830828
X5alignment is lost and flushed silently697714660
—unmutated baseline000

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

X3 is the shim. It is the mutation that implements "our device is allowed to do that" in code, and it scores 17 000 — not because a single disagreement is catastrophic, but because the 256-pair sweep compares every combination and 240 of them should disagree.

X2 is the one this chapter is named for. Permissive mode masks the failure and forgets to count it, so a permissive run comes back clean. It scores only 3400 — an order of magnitude below X3 — and that ratio is the point: the mutation that makes a run silently clean is far harder to detect than the one that makes it wrong, because the only evidence is a number that did not change.

X1 is the smallest of the three headline mutations at ~1600, and it is the most insidious. It compares PIDs and ignores meaning, so both sides agree on what arrived and disagree on what it means — and the adapter says nothing. That is precisely the disagreement worth owning a VIP for.

13. Debugging Walkthrough: The Filter That Hid Its Own Reason

The report. A USB device passes a commercial VIP's compliance suite. It fails interoperability testing against a particular host chipset, on enumeration, reproducibly.

Step 1 — what does the VIP say? Nothing. Clean run, 100% of its compliance checks.

Step 2 — what does the host do differently? It issues a GET_DESCRIPTOR with a wLength larger than the descriptor. The device returns the descriptor and then a zero-length packet. The host times out.

Step 3 — is that legal? Yes: a short transfer terminates when the data is shorter than requested, and the zero-length packet is only required when the transfer is an exact multiple of the maximum packet size. The device is sending one when it must not, and the host is waiting for the status stage.

Step 4 — so why did the VIP not flag it? Grep the adapter. There is a filter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // added 2023-04-11, VIP flags spurious ZLP, our device is fine
  if (vip_err == VIP_UNEXPECTED_ZLP) return;   // <-- three years old

Step 5 — was the device fine? No. The comment records somebody's conclusion and none of their reasoning, and the person who wrote it left. The VIP had been reporting the actual interoperability bug on every single run for three years, and the adapter had been deleting it.

Step 6 — what the permissive counter would have done. Nothing, on its own — the filter would still have masked it. But n_masked would have read a few thousand rather than zero, on every run, and "we mask three thousand VIP disagreements per regression" is a sentence somebody eventually asks about. Zero is a sentence nobody asks about.

14. UVM: Wrapping a Foreign VIP

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A VIP arrives as a UVM agent with its own sequence item, its own monitor,
// and its own idea of what an "event" is. The wrapper's job is to bring its
// stream into your environment WITHOUT deciding anything.
//
// Note what this class does not contain: no filtering, no severity
// downgrades, no `if (err == X) return`. Every transformation in it is a
// FORMAT change, and format changes are the only kind that are safe to make
// silently.
class usb_vip_wrapper extends uvm_component;
  `uvm_component_utils(usb_vip_wrapper)

  // The foreign agent's analysis port comes in here...
  uvm_analysis_imp #(vendor_usb_item, usb_vip_wrapper) vip_ap;
  // ...and a normalised view goes out here.
  uvm_analysis_port #(usb_view_item) out_ap;

  // Counted, not filtered: every item the wrapper could not map onto our
  // own view. A VIP that reports something we have no representation for is
  // itself a finding -- it usually means a protocol feature nobody on this
  // side has modelled.
  int unsigned n_unmapped;
  int unsigned n_unmapped_kind[string];

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

  function void write(vendor_usb_item t);
    usb_view_item v = usb_view_item::type_id::create("v");

    // ---- FORMAT normalisation only. ----
    v.pid  = t.packet_id[3:0];
    v.view = map_view(t.transaction_kind);

    if (v.view == VIEW_UNKNOWN) begin
      // NOT dropped. A VIP event we cannot represent is counted by kind,
      // so "the VIP keeps telling us about something we do not model" is
      // visible as a number rather than as silence.
      n_unmapped++;
      n_unmapped_kind[t.transaction_kind.name()]++;
    end

    out_ap.write(v);
  endfunction

  function usb_view_e map_view(vendor_kind_e k);
    case (k)
      VENDOR_TOKEN_OUT, VENDOR_TOKEN_IN,
      VENDOR_TOKEN_SETUP, VENDOR_TOKEN_SOF : return VIEW_TOKEN;
      VENDOR_DATA0, VENDOR_DATA1           : return VIEW_DATA;
      VENDOR_ACK, VENDOR_NAK, VENDOR_STALL : return VIEW_HANDSHAKE;
      default                              : return VIEW_UNKNOWN;
    endcase
  endfunction

  function void report_phase(uvm_phase phase);
    if (n_unmapped > 0)
      `uvm_warning("VIP_UNMAPPED",
        $sformatf("%0d VIP events had no representation in our view model: %p -- this is usually a protocol feature nobody on this side has modelled, not a VIP defect",
                  n_unmapped, n_unmapped_kind))
  endfunction
endclass

// ---- And the comparator, which is the hardware block above. ----
class usb_vip_comparator extends uvm_scoreboard;
  `uvm_component_utils(usb_vip_comparator)

  uvm_analysis_imp_vip #(usb_view_item, usb_vip_comparator) vip_ap;
  uvm_analysis_imp_dut #(usb_view_item, usb_vip_comparator) dut_ap;

  usb_view_item vip_q[$];
  usb_view_item dut_q[$];

  // Configurable, and deliberately NOT settable to zero or to something
  // enormous: a bound that can be disabled by configuration is not a bound.
  int unsigned skew_max = 4;

  // strict is a CONFIGURATION, and the default is strict. A permissive
  // default is how an environment acquires a hundred masked disagreements
  // that nobody ever decided to mask.
  bit strict = 1;

  int unsigned n_agree, n_masked;
  int unsigned n_dis[string];

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

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    void'(uvm_config_db #(int unsigned)::get(this, "", "skew_max", skew_max));
    void'(uvm_config_db #(bit)::get(this, "", "strict", strict));
    if (skew_max < 1 || skew_max > 64)
      `uvm_fatal("CFG",
        $sformatf("skew_max=%0d -- an alignment bound that can be set to zero or to a very large number is not a bound",
                  skew_max))
  endfunction

  function void write_vip(usb_view_item t); vip_q.push_back(t); compare(); endfunction
  function void write_dut(usb_view_item t); dut_q.push_back(t); compare(); endfunction

  function void compare();
    while (vip_q.size() > 0 && dut_q.size() > 0) begin
      usb_view_item v = vip_q.pop_front();
      usb_view_item d = dut_q.pop_front();
      string cause = "";

      if (v.pid != d.pid)
        cause = "PID";
      else if (v.view != d.view)
        // The disagreement worth owning a VIP for: both sides saw the same
        // packet and read it differently, which is always a specification
        // reading rather than a decode bug.
        cause = "VIEW";

      if (cause == "") begin
        n_agree++;
      end else begin
        // The CAUSE is counted either way. `strict` decides whether the run
        // fails; it does not decide whether the disagreement happened.
        n_dis[cause]++;
        if (strict)
          `uvm_error("VIP_DISAGREE",
            $sformatf("%s: VIP read pid=0x%01h view=%s, design read pid=0x%01h view=%s -- one of these two readings of the specification is wrong and the adapter cannot tell you which",
                      cause, v.pid, v.view.name(), d.pid, d.view.name()))
        else begin
          n_masked++;
          `uvm_info("VIP_MASKED",
            $sformatf("%s disagreement masked by permissive mode", cause), UVM_MEDIUM)
        end
      end
    end

    // ---- THE ALIGNMENT BOUND. ----
    if (vip_q.size() > skew_max || dut_q.size() > skew_max) begin
      n_dis["SKEW"]++;
      `uvm_error("VIP_SKEW",
        $sformatf("one side is %0d events ahead of the other -- they are no longer skewed, they have lost alignment, and every comparison after this point would be between unrelated events",
                  (vip_q.size() > dut_q.size()) ? vip_q.size() : dut_q.size()))
      vip_q.delete();
      dut_q.delete();
    end
  endfunction

  // ---- THE DRAIN. An event only one side ever reported. ----
  function void check_phase(uvm_phase phase);
    foreach (vip_q[i])
      `uvm_error("VIP_UNPAIRED",
        $sformatf("the VIP reported pid=0x%01h and the design never did", vip_q[i].pid))
    foreach (dut_q[i])
      `uvm_error("VIP_UNPAIRED",
        $sformatf("the design reported pid=0x%01h and the VIP never did", dut_q[i].pid))
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("VIP", $sformatf("agreed=%0d masked=%0d disagreements=%p",
                               n_agree, n_masked, n_dis), UVM_LOW)

    // ---- A permissive run does not come back clean. ----
    if (n_masked > 0)
      `uvm_warning("VIP_MASKED",
        $sformatf("%0d disagreements were masked by permissive mode -- that is %0d specification arguments nobody has had yet, and the number is the only record that they exist",
                  n_masked, n_masked))

    // ---- A comparator that compared nothing is not a passing comparator. --
    if (n_agree == 0 && n_masked == 0)
      `uvm_error("VIP",
        "the two views were never compared at all -- either the VIP's port is not connected or it produced no events, and its silence means nothing")
  endfunction
endclass

15. Common Misconceptions

"The VIP is the golden reference." It is a second implementation. It can be wrong, and on the specification's newer corners it frequently is.

"The VIP's coverage is our coverage." It is the coverage of the bins its authors declared for the revision they targeted.

"The adapter should make the interfaces line up." Format, yes. Meaning, never.

"This one disagreement is a known false positive." Then it is a specification argument, and it has to be won in writing rather than filtered in a shim.

"A filter with an explanatory comment is documented." The comment records a conclusion and none of the reasoning, and it never expires.

"Permissive mode is for getting through the afternoon." It is — and it must cost a visible number, or the afternoon becomes three years.

"The skew window is too small; widen it." Past the bound the two sides are not skewed, they are unaligned, and widening converts one loud failure into a stream of nonsense.

"An event only one side saw is skew that had not settled." At end of test there is no more traffic. One implementation saw something the other never did.

"Strict or permissive decides whether it is a bug." It decides whether the run fails. The disagreement happened either way.

16. Exercises

1. X3 scores ~17 000 and X2 ~3 400, though X2 is the more dangerous bug. Explain the ratio from what each does to the evidence, and say which you would rather have in a regression you do not read every day.

2. X1's score is almost exactly three times the baseline view-disagreement count in all three languages. Derive the three, then predict X7's score from n_unpaired and check it.

3. The skew check cannot fire in the same cycle as a comparison. Prove it from the order of the three steps, then show which reordering breaks it.

4. Write the three artefacts §13 says would make a filter an owned decision, for the ZLP case specifically.

5. A VIP reports an event your view model has no representation for. Argue whether that should be an error, a warning or a counted statistic, and what changes your answer.

6. Add a third observer — a bus analyser trace replayed from silicon. What changes in the comparator, and what does a 2-against-1 disagreement now let you conclude that a 1-against-1 did not?

17. Summary

IdeaWhy it matters
A VIP is a second implementationwhere it and the design differ, one is wrong
The adapter makes a disagreement visibleit does not resolve one
A filter is a specification argumentit has to be won, not implemented in a shim
Permissive mode must countor a masked run comes back clean
Strict is the defaulta permissive default is how masks accumulate unnoticed
PID vs VIEW disagreements go to different peoplea decode bug and a reading of the spec
The alignment window is boundedpast it they are unaligned, not skewed
Widening the window is the wrong reflexit buries one loud failure in quiet nonsense
The loss is reported once and flushedor every later event repeats it
An unpaired event at end of test is a findingone implementation saw what the other never did
A VIP's coverage is its model's coverageits bins, its revision, its configurations
80/80 skew x input, 256/256 PID pairs, 16/16 view pairs7 mutations, all killed in 3 languages

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o va_v.out va_v.v va_v_tb.v && ./va_v.out
SystemVerilogiverilog -g2012 -o va_sv.out va_sv.sv va_sv_tb.sv && ./va_sv.out
VHDL-2008 analysenvc --std=2008 -a va_vhdl.vhd va_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_va_vhdl
VHDL-2008 runnvc --std=2008 -r tb_va_vhdl
One mutationiverilog -g2005 -DMUT_X3 -o mm va_v_mut.v va_v_tb.v && ./mm

All three implementations pass with 0 errors: all 256 PID pairs compared with agreement on exactly the diagonal, all 16 view pairs compared, every alignment skew crossed with every input combination, and the same disagreement driven in both strict and permissive mode with the cause counted in both.


Chapter 24.6 — UVM Architecture for USB assembles all of it. A USB environment has two agents on one wire, and that is the structural problem the whole architecture is built around: host and device both drive the same differential pair, at different times, with a turnaround between them. An environment that does not model the turnaround creates contention the real bus never would — and then spends weeks debugging its own testbench.

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.