Skip to content
VLSI Mentor

USB · Module 24

UVM Architecture for USB

A USB environment has two agents on one wire, and the turnaround between them is the structural problem the whole architecture is built around — model it, or debug your own testbench for weeks.

This chapter assembles the module. The five components built so far all assumed something was feeding them; this one is about what feeds them, and about the single structural fact that shapes the whole environment.

1. Two Agents, One Wire

A USB environment has a host agent and a device agent, and they drive the same differential pair. Not two interfaces that happen to be connected — one wire, half duplex, with the two sides taking turns.

That is not an implementation detail of the driver. It is the shape of the architecture, and almost every question about a USB environment — where the monitor sits, what the sequencer may do, why the scoreboard sees what it sees — comes back to it.

2. The Turnaround Is Not Optional and It Is Not Zero

When the host finishes a token that requires a response, it stops driving and the device starts. Between those two events there is a gap:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   the host's drivers must actually release the line
   the line must settle
   the device must have decoded the token and decided to answer

   start EARLY  ->  both sides driving at once: contention,
                    the line at an undefined level, both sides
                    seeing corruption, and NEITHER seeing a cause

   start LATE   ->  the host has already timed out, and reports
                    it as "the device did not answer"

3. Ownership Is Derived, Not Negotiated

There is no arbitration on a USB bus. The host owns it by default and hands it over implicitly, by sending a packet that requires a response. The device never initiates anything.

So a device driving an idle bus is not "an agent that won arbitration early". It is an agent doing something no real device can do — and letting it through produces a stimulus the design will never see in silicon, which is worse than useless because it generates work.

4. The Machine

usb_bus_ownership — five states, and two of them are the gap

A five-state bus ownership machine. IDLE moves to HOST when the host drives, and back when the host releases. HOST moves to TURN_DEV on a handover. TURN_DEV moves to DEV once TA_MIN cycles have passed and the device drives, or back to IDLE if the turnaround expires. DEV moves to TURN_HST when the device releases, and TURN_HST moves back to HOST once TA_MIN cycles have passed.B_IDLEB_HOSTB_TURN_DEVB_DEVB_TURN_HSThost driveshost drivesreleasereleasehandoverhandoverafter TA_MINafter TA_MINTA_MAX expiresTA_MAXexpiresreleasereleaseafter TA_MINafter TA_MIN
The two turnaround states are the ones in which nobody owns the wire. An environment whose ownership model has no such state has nowhere to put the gap, and so does not have one. Contention, a non-owner driving, and driving past the hold bound all return to IDLE from any state and are omitted here for clarity.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  usb_bus_ownership  #(TA_MIN = 2, TA_MAX = 8, HOLD_MAX = 16)

  inputs                     outputs
  ------                     -------
  host_drive / dev_drive     state   5 states
  handover   this packet     owner   NONE / HOST / DEV
             needs a reply   ta_age / hold_age
  rel_bus    finished        grant_pulse / err_pulse
                             err_code  CONTENTION / EARLY /
                                       LATE / OVERHOLD / STOLEN

  n_grants n_handovers n_contention
  n_early n_late n_overhold n_stolen

  `release` is a Verilog keyword, hence rel_bus.

5. Contention Is Checked First, in Every State

Two agents driving one wire is not a state-machine transition. It is a physical fault, and once it has happened nothing else observed that cycle means anything.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // ---- CONTENTION is checked FIRST, in every state, before anything else.
  //
  // Two agents driving one wire is not a state-machine transition; it is a
  // physical fault, and once it has happened nothing else observed that
  // cycle means anything. Checking it inside the per-state logic -- which is
  // the natural way to write this block -- means every state has to remember
  // to check it, and one of them will not.
  wire both_drive = host_drive && dev_drive;

The natural way to write this block puts the check inside each state's logic, because that is where the drive signals are already being examined. Five states, five places to remember, and one of them will not. Hoisting it above the case statement makes it structurally impossible to miss — and the suite drives contention from all five states specifically to catch the version that did not.

6. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_bus_ownership -- the structural problem a USB verification environment
// is built around, and the thing an environment that ignores it spends weeks
// debugging.
//
// TWO AGENTS, ONE WIRE
//
// A USB environment has a host agent and a device agent, and they drive the
// SAME differential pair. Not two interfaces that happen to be connected --
// one wire, half duplex, with the two sides taking turns.
//
// That is not an implementation detail of the driver. It is the shape of the
// whole architecture, and almost every question about a USB environment --
// where the monitor sits, what the sequencer may do, why the scoreboard sees
// what it sees -- comes back to it.
//
// THE TURNAROUND IS NOT OPTIONAL AND IT IS NOT ZERO
//
// When the host finishes a token that requires a response, it stops driving
// and the device starts. Between those two events there is a gap:
//
//     the host's drivers must actually release the line
//     the line must settle
//     the device must have decoded the token and decided to answer
//
// Drive too EARLY and both sides are driving at once, which is contention:
// the line is at an undefined level, both sides see corruption, and NEITHER
// sees a cause. Drive too LATE and the host has already timed out.
//
//     start early  ->  contention, and no symptom that points at it
//     start late   ->  a timeout the host reports as "no response"
//
// AN ENVIRONMENT THAT DOES NOT MODEL IT DEBUGS ITSELF
//
// This is the practical consequence and it is worth stating bluntly. A
// two-agent environment whose drivers do not respect a turnaround produces
// contention that the real bus never would, on a schedule determined by
// testbench scheduling rather than by the design. The failures are
// intermittent, they move when you add a $display, and they look exactly
// like a design bug.
//
// Weeks go into that. The fix is a block like this one: ownership is a
// MODELLED, CHECKED property of the environment, and a driver that drives
// when it does not own the bus is a testbench error reported as such --
// immediately, with a name, and distinguishable from the design's faults.
//
// OWNERSHIP IS DERIVED, NOT NEGOTIATED
//
// There is no arbitration on a USB bus. The host owns it by default and
// hands it over IMPLICITLY, by sending a packet that requires a response.
// The device never initiates anything.
//
// So a device driving an idle bus is not "an agent that won arbitration
// early". It is an agent doing something no real device can do, and the
// environment must say so rather than letting it through and producing a
// stimulus the design will never see in silicon.
module usb_bus_ownership #(
  parameter integer TA_MIN   = 2,    // cycles before the new owner may drive
  parameter integer TA_MAX   = 8,    // cycles before the turnaround is dead
  parameter integer HOLD_MAX = 16    // cycles an owner may drive continuously
) (
  input  wire       clk,
  input  wire       rst_n,

  input  wire       host_drive,  // the host agent is driving the wire
  input  wire       dev_drive,   // the device agent is driving the wire
  input  wire       handover,    // this packet requires a response
  input  wire       rel_bus,     // the current owner has finished
                                 // (`release` is a Verilog keyword)
  input  wire       eot,

  output wire [2:0] state,
  output wire [1:0] owner,
  output wire [4:0] ta_age,      // cycles spent in the turnaround
  output wire [4:0] hold_age,    // cycles the owner has been driving
  output wire       grant_pulse,
  output wire       err_pulse,
  output wire [2:0] err_code,

  output reg [31:0] n_grants,
  output reg [31:0] n_handovers,
  output reg [31:0] n_contention,
  output reg [31:0] n_early,
  output reg [31:0] n_late,
  output reg [31:0] n_overhold,
  output reg [31:0] n_stolen
);

  localparam [2:0] B_IDLE     = 3'd0,  // nobody driving; the host may start
                   B_HOST     = 3'd1,  // the host owns the wire
                   B_TURN_DEV = 3'd2,  // handed over; the device may start
                                       // once TA_MIN cycles have passed
                   B_DEV      = 3'd3,  // the device owns the wire
                   B_TURN_HST = 3'd4;  // returning; the host may start again

  localparam [1:0] O_NONE = 2'd0, O_HOST = 2'd1, O_DEV = 2'd2;

  localparam [2:0] E_NONE       = 3'd0,
                   E_CONTENTION = 3'd1,  // BOTH agents driving at once
                   E_EARLY      = 3'd2,  // drove before the turnaround ended
                   E_LATE       = 3'd3,  // the turnaround expired unanswered
                   E_OVERHOLD   = 3'd4,  // drove past HOLD_MAX
                   E_STOLEN     = 3'd5;  // drove without owning the wire

  reg [2:0] st_r;
  reg [4:0] ta_r, hd_r;
  reg [2:0] ec_r;
  reg       gr_r, er_r;

  assign state       = st_r;
  assign owner       = (st_r == B_HOST) ? O_HOST
                     : (st_r == B_DEV)  ? O_DEV
                     :                    O_NONE;
  assign ta_age      = ta_r;
  assign hold_age    = hd_r;
  assign grant_pulse = gr_r;
  assign err_pulse   = er_r;
  assign err_code    = ec_r;

  // ---- CONTENTION is checked FIRST, in every state, before anything else.
  //
  // Two agents driving one wire is not a state-machine transition; it is a
  // physical fault, and once it has happened nothing else observed that
  // cycle means anything. Checking it inside the per-state logic -- which is
  // the natural way to write this block -- means every state has to remember
  // to check it, and one of them will not.
  wire both_drive = host_drive && dev_drive;

  reg [2:0] st_n, ec_n;
  reg [4:0] ta_n, hd_n;
  reg       gr_n, er_n, hv_n;

  always @* begin
    st_n = st_r;
    ta_n = ta_r;
    hd_n = hd_r;
    ec_n = E_NONE;
    gr_n = 1'b0;
    er_n = 1'b0;
    hv_n = 1'b0;

    if (eot) begin
      // End of test returns the wire to nobody. An agent still driving here
      // is not reported as an error: the run is over, and the interesting
      // report at this point is the counters.
      st_n = B_IDLE;
      ta_n = 5'd0;
      hd_n = 5'd0;
    end else if (both_drive) begin
      // ---- CONTENTION. Both agents on the wire. ----
      //
      // The line is at an undefined level, both sides will see corruption,
      // and neither will see a cause. Reported here and nowhere else, so it
      // cannot be missed by a state that forgot to look.
      er_n = 1'b1; ec_n = E_CONTENTION;
      st_n = B_IDLE; ta_n = 5'd0; hd_n = 5'd0;
    end else begin
      case (st_r)
        // ------------------------------------------------------------------
        B_IDLE: begin
          hd_n = 5'd0;
          ta_n = 5'd0;
          if (dev_drive) begin
            // ---- A device driving an idle bus. ----
            //
            // There is no arbitration on this bus. The host owns it by
            // default and the device never initiates, so this is an agent
            // doing something no real device can do -- and letting it
            // through produces a stimulus the design will never see.
            er_n = 1'b1; ec_n = E_STOLEN;
          end else if (host_drive) begin
            st_n = B_HOST;
            gr_n = 1'b1;
            hd_n = 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_HOST: begin
          if (dev_drive) begin
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (hd_r >= HOLD_MAX[4:0]) begin
            // ---- Driving past the hold bound. ----
            //
            // An agent that never stops is the worst of the failures here,
            // because the other agent's response collides with it and the
            // contention is attributed to whoever spoke second.
            er_n = 1'b1; ec_n = E_OVERHOLD;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (handover) begin
            // The packet requires a response: ownership passes, through a
            // turnaround rather than directly.
            st_n = B_TURN_DEV;
            ta_n = 5'd0;
            hd_n = 5'd0;
            hv_n = 1'b1;
          end else if (rel_bus) begin
            st_n = B_IDLE;
            hd_n = 5'd0;
          end else if (host_drive) begin
            hd_n = hd_r + 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_TURN_DEV: begin
          if (host_drive) begin
            // The host has handed over. Driving again now is contention
            // waiting to happen.
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (dev_drive && (ta_r < TA_MIN[4:0])) begin
            // ---- TOO EARLY. ----
            //
            // The host's drivers have not released the line yet. This is
            // the failure with no symptom that points at it: both sides see
            // corruption and neither sees a cause.
            er_n = 1'b1; ec_n = E_EARLY;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (dev_drive) begin
            st_n = B_DEV;
            gr_n = 1'b1;
            hd_n = 5'd1;
            ta_n = 5'd0;
          end else if (ta_r >= TA_MAX[4:0]) begin
            // ---- TOO LATE. The device never answered. ----
            er_n = 1'b1; ec_n = E_LATE;
            st_n = B_IDLE; ta_n = 5'd0;
          end else begin
            ta_n = ta_r + 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_DEV: begin
          if (host_drive) begin
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (hd_r >= HOLD_MAX[4:0]) begin
            er_n = 1'b1; ec_n = E_OVERHOLD;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (rel_bus || handover) begin
            // The device has finished. The wire goes back to the host, and
            // it goes back through a turnaround for exactly the same reason
            // it came the other way through one.
            st_n = B_TURN_HST;
            ta_n = 5'd0;
            hd_n = 5'd0;
          end else if (dev_drive) begin
            hd_n = hd_r + 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_TURN_HST: begin
          if (dev_drive) begin
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (host_drive && (ta_r < TA_MIN[4:0])) begin
            er_n = 1'b1; ec_n = E_EARLY;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (host_drive) begin
            st_n = B_HOST;
            gr_n = 1'b1;
            hd_n = 5'd1;
            ta_n = 5'd0;
          end else if (ta_r >= TA_MAX[4:0]) begin
            // The host not taking the wire back is not an error on the
            // device's part, but it is a stalled bus, and it is reported
            // for the same reason chapter 24.1's timeout is: nothing else
            // in this block fires when NOTHING happens.
            er_n = 1'b1; ec_n = E_LATE;
            st_n = B_IDLE; ta_n = 5'd0;
          end else begin
            ta_n = ta_r + 5'd1;
          end
        end

        default: st_n = B_IDLE;
      endcase
    end
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r         <= B_IDLE;
      ta_r         <= 5'd0;
      hd_r         <= 5'd0;
      ec_r         <= E_NONE;
      gr_r         <= 1'b0;
      er_r         <= 1'b0;
      n_grants     <= 32'd0;
      n_handovers  <= 32'd0;
      n_contention <= 32'd0;
      n_early      <= 32'd0;
      n_late       <= 32'd0;
      n_overhold   <= 32'd0;
      n_stolen     <= 32'd0;
    end else begin
      st_r <= st_n;
      ta_r <= ta_n;
      hd_r <= hd_n;
      ec_r <= ec_n;
      gr_r <= gr_n;
      er_r <= er_n;

      if (gr_n) n_grants    <= n_grants + 32'd1;
      if (hv_n) n_handovers <= n_handovers + 32'd1;

      // The per-cause counters are driven by the SAME pulse as the error,
      // so they sum to the error total by construction (chapter 23.4).
      if (er_n) begin
        case (ec_n)
          E_CONTENTION: n_contention <= n_contention + 32'd1;
          E_EARLY:      n_early      <= n_early      + 32'd1;
          E_LATE:       n_late       <= n_late       + 32'd1;
          E_OVERHOLD:   n_overhold   <= n_overhold   + 32'd1;
          E_STOLEN:     n_stolen     <= n_stolen     + 32'd1;
          default: ;
        endcase
      end
    end
  end
endmodule

7. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_bus_ownership -- the structural problem a USB verification environment
// is built around, and the thing an environment that ignores it spends weeks
// debugging.
//
// TWO AGENTS, ONE WIRE
//
// A USB environment has a host agent and a device agent, and they drive the
// SAME differential pair. Not two interfaces that happen to be connected --
// one wire, half duplex, with the two sides taking turns.
//
// That is not an implementation detail of the driver. It is the shape of the
// whole architecture, and almost every question about a USB environment --
// where the monitor sits, what the sequencer may do, why the scoreboard sees
// what it sees -- comes back to it.
//
// THE TURNAROUND IS NOT OPTIONAL AND IT IS NOT ZERO
//
// When the host finishes a token that requires a response, it stops driving
// and the device starts. Between those two events there is a gap:
//
//     the host's drivers must actually release the line
//     the line must settle
//     the device must have decoded the token and decided to answer
//
// Drive too EARLY and both sides are driving at once, which is contention:
// the line is at an undefined level, both sides see corruption, and NEITHER
// sees a cause. Drive too LATE and the host has already timed out.
//
//     start early  ->  contention, and no symptom that points at it
//     start late   ->  a timeout the host reports as "no response"
//
// AN ENVIRONMENT THAT DOES NOT MODEL IT DEBUGS ITSELF
//
// This is the practical consequence and it is worth stating bluntly. A
// two-agent environment whose drivers do not respect a turnaround produces
// contention that the real bus never would, on a schedule determined by
// testbench scheduling rather than by the design. The failures are
// intermittent, they move when you add a $display, and they look exactly
// like a design bug.
//
// Weeks go into that. The fix is a block like this one: ownership is a
// MODELLED, CHECKED property of the environment, and a driver that drives
// when it does not own the bus is a testbench error reported as such --
// immediately, with a name, and distinguishable from the design's faults.
//
// OWNERSHIP IS DERIVED, NOT NEGOTIATED
//
// There is no arbitration on a USB bus. The host owns it by default and
// hands it over IMPLICITLY, by sending a packet that requires a response.
// The device never initiates anything.
//
// So a device driving an idle bus is not "an agent that won arbitration
// early". It is an agent doing something no real device can do, and the
// environment must say so rather than letting it through and producing a
// stimulus the design will never see in silicon.
package usb_bus_pkg;
  // The five bus states. The two turnaround states are not decoration: they
  // are the states in which NOBODY owns the wire, and an environment whose
  // ownership model has no such state will let an agent drive into the gap.
  typedef enum logic [2:0] {
    B_IDLE     = 3'd0,   // nobody driving; the host may start
    B_HOST     = 3'd1,   // the host owns the wire
    B_TURN_DEV = 3'd2,   // handed over; the device may start once TA_MIN
                         // cycles have passed
    B_DEV      = 3'd3,   // the device owns the wire
    B_TURN_HST = 3'd4    // returning; the host may start again
  } bus_state_e;

  typedef enum logic [1:0] {
    O_NONE = 2'd0, O_HOST = 2'd1, O_DEV = 2'd2
  } owner_e;

  typedef enum logic [2:0] {
    E_NONE       = 3'd0,
    E_CONTENTION = 3'd1,   // BOTH agents driving at once
    E_EARLY      = 3'd2,   // drove before the turnaround ended
    E_LATE       = 3'd3,   // the turnaround expired unanswered
    E_OVERHOLD   = 3'd4,   // drove past HOLD_MAX
    E_STOLEN     = 3'd5    // drove without owning the wire
  } bus_err_e;
endpackage

module usb_bus_ownership
  import usb_bus_pkg::*;
 #(
  parameter int TA_MIN   = 2,    // cycles before the new owner may drive
  parameter int TA_MAX   = 8,    // cycles before the turnaround is dead
  parameter int HOLD_MAX = 16    // cycles an owner may drive continuously
) (
  input  logic      clk,
  input  logic      rst_n,

  input  logic      host_drive,  // the host agent is driving the wire
  input  logic      dev_drive,   // the device agent is driving the wire
  input  logic      handover,    // this packet requires a response
  input  logic      rel_bus,     // the current owner has finished
                                 // (`release` is a Verilog keyword)
  input  logic      eot,

  output bus_state_e state,
  output owner_e     owner,
  output logic [4:0] ta_age,      // cycles spent in the turnaround
  output logic [4:0] hold_age,    // cycles the owner has been driving
  output logic      grant_pulse,
  output logic      err_pulse,
  output bus_err_e   err_code,

  output logic [31:0] n_grants,
  output logic [31:0] n_handovers,
  output logic [31:0] n_contention,
  output logic [31:0] n_early,
  output logic [31:0] n_late,
  output logic [31:0] n_overhold,
  output logic [31:0] n_stolen
);

  bus_state_e st_r;
  bus_err_e   ec_r;
  logic [4:0] ta_r, hd_r;
  logic       gr_r, er_r;

  assign state       = st_r;

  // Written as if/else rather than a ternary chain: an enum-valued ternary
  // needs an explicit cast in Icarus, and the cast would turn a type error
  // into a silent truncation.
  always_comb begin
    if      (st_r == B_HOST) owner = O_HOST;
    else if (st_r == B_DEV)  owner = O_DEV;
    else                     owner = O_NONE;
  end

  assign ta_age      = ta_r;
  assign hold_age    = hd_r;
  assign grant_pulse = gr_r;
  assign err_pulse   = er_r;
  assign err_code    = ec_r;

  // ---- CONTENTION is checked FIRST, in every state, before anything else.
  //
  // Two agents driving one wire is not a state-machine transition; it is a
  // physical fault, and once it has happened nothing else observed that
  // cycle means anything. Checking it inside the per-state logic -- which is
  // the natural way to write this block -- means every state has to remember
  // to check it, and one of them will not.
  logic both_drive;
  assign both_drive = host_drive && dev_drive;

  bus_state_e st_n;
  bus_err_e   ec_n;
  logic [4:0] ta_n, hd_n;
  logic       gr_n, er_n, hv_n;

  always_comb begin
    st_n = st_r;
    ta_n = ta_r;
    hd_n = hd_r;
    ec_n = E_NONE;
    gr_n = 1'b0;
    er_n = 1'b0;
    hv_n = 1'b0;

    if (eot) begin
      // End of test returns the wire to nobody. An agent still driving here
      // is not reported as an error: the run is over, and the interesting
      // report at this point is the counters.
      st_n = B_IDLE;
      ta_n = 5'd0;
      hd_n = 5'd0;
    end else if (both_drive) begin
      // ---- CONTENTION. Both agents on the wire. ----
      //
      // The line is at an undefined level, both sides will see corruption,
      // and neither will see a cause. Reported here and nowhere else, so it
      // cannot be missed by a state that forgot to look.
      er_n = 1'b1; ec_n = E_CONTENTION;
      st_n = B_IDLE; ta_n = 5'd0; hd_n = 5'd0;
    end else begin
      case (st_r)
        // ------------------------------------------------------------------
        B_IDLE: begin
          hd_n = 5'd0;
          ta_n = 5'd0;
          if (dev_drive) begin
            // ---- A device driving an idle bus. ----
            //
            // There is no arbitration on this bus. The host owns it by
            // default and the device never initiates, so this is an agent
            // doing something no real device can do -- and letting it
            // through produces a stimulus the design will never see.
            er_n = 1'b1; ec_n = E_STOLEN;
          end else if (host_drive) begin
            st_n = B_HOST;
            gr_n = 1'b1;
            hd_n = 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_HOST: begin
          if (dev_drive) begin
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (hd_r >= 5'(HOLD_MAX)) begin
            // ---- Driving past the hold bound. ----
            //
            // An agent that never stops is the worst of the failures here,
            // because the other agent's response collides with it and the
            // contention is attributed to whoever spoke second.
            er_n = 1'b1; ec_n = E_OVERHOLD;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (handover) begin
            // The packet requires a response: ownership passes, through a
            // turnaround rather than directly.
            st_n = B_TURN_DEV;
            ta_n = 5'd0;
            hd_n = 5'd0;
            hv_n = 1'b1;
          end else if (rel_bus) begin
            st_n = B_IDLE;
            hd_n = 5'd0;
          end else if (host_drive) begin
            hd_n = hd_r + 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_TURN_DEV: begin
          if (host_drive) begin
            // The host has handed over. Driving again now is contention
            // waiting to happen.
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (dev_drive && (ta_r < 5'(TA_MIN))) begin
            // ---- TOO EARLY. ----
            //
            // The host's drivers have not released the line yet. This is
            // the failure with no symptom that points at it: both sides see
            // corruption and neither sees a cause.
            er_n = 1'b1; ec_n = E_EARLY;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (dev_drive) begin
            st_n = B_DEV;
            gr_n = 1'b1;
            hd_n = 5'd1;
            ta_n = 5'd0;
          end else if (ta_r >= 5'(TA_MAX)) begin
            // ---- TOO LATE. The device never answered. ----
            er_n = 1'b1; ec_n = E_LATE;
            st_n = B_IDLE; ta_n = 5'd0;
          end else begin
            ta_n = ta_r + 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_DEV: begin
          if (host_drive) begin
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (hd_r >= 5'(HOLD_MAX)) begin
            er_n = 1'b1; ec_n = E_OVERHOLD;
            st_n = B_IDLE; hd_n = 5'd0;
          end else if (rel_bus || handover) begin
            // The device has finished. The wire goes back to the host, and
            // it goes back through a turnaround for exactly the same reason
            // it came the other way through one.
            st_n = B_TURN_HST;
            ta_n = 5'd0;
            hd_n = 5'd0;
          end else if (dev_drive) begin
            hd_n = hd_r + 5'd1;
          end
        end

        // ------------------------------------------------------------------
        B_TURN_HST: begin
          if (dev_drive) begin
            er_n = 1'b1; ec_n = E_STOLEN;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (host_drive && (ta_r < 5'(TA_MIN))) begin
            er_n = 1'b1; ec_n = E_EARLY;
            st_n = B_IDLE; ta_n = 5'd0;
          end else if (host_drive) begin
            st_n = B_HOST;
            gr_n = 1'b1;
            hd_n = 5'd1;
            ta_n = 5'd0;
          end else if (ta_r >= 5'(TA_MAX)) begin
            // The host not taking the wire back is not an error on the
            // device's part, but it is a stalled bus, and it is reported
            // for the same reason chapter 24.1's timeout is: nothing else
            // in this block fires when NOTHING happens.
            er_n = 1'b1; ec_n = E_LATE;
            st_n = B_IDLE; ta_n = 5'd0;
          end else begin
            ta_n = ta_r + 5'd1;
          end
        end

        default: st_n = B_IDLE;
      endcase
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r         <= B_IDLE;
      ta_r         <= 5'd0;
      hd_r         <= 5'd0;
      ec_r         <= E_NONE;
      gr_r         <= 1'b0;
      er_r         <= 1'b0;
      n_grants     <= 32'd0;
      n_handovers  <= 32'd0;
      n_contention <= 32'd0;
      n_early      <= 32'd0;
      n_late       <= 32'd0;
      n_overhold   <= 32'd0;
      n_stolen     <= 32'd0;
    end else begin
      st_r <= st_n;
      ta_r <= ta_n;
      hd_r <= hd_n;
      ec_r <= ec_n;
      gr_r <= gr_n;
      er_r <= er_n;

      if (gr_n) n_grants    <= n_grants + 32'd1;
      if (hv_n) n_handovers <= n_handovers + 32'd1;

      // The per-cause counters are driven by the SAME pulse as the error,
      // so they sum to the error total by construction (chapter 23.4).
      if (er_n) begin
        case (ec_n)
          E_CONTENTION: n_contention <= n_contention + 32'd1;
          E_EARLY:      n_early      <= n_early      + 32'd1;
          E_LATE:       n_late       <= n_late       + 32'd1;
          E_OVERHOLD:   n_overhold   <= n_overhold   + 32'd1;
          E_STOLEN:     n_stolen     <= n_stolen     + 32'd1;
          default: ;
        endcase
      end
    end
  end
endmodule

8. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb_bus_ownership -- the structural problem a USB verification environment
-- is built around, and the thing an environment that ignores it spends weeks
-- debugging.
--
-- TWO AGENTS, ONE WIRE
--
-- A USB environment has a host agent and a device agent, and they drive the
-- SAME differential pair. Not two interfaces that happen to be connected --
-- one wire, half duplex, with the two sides taking turns.
--
-- That is not an implementation detail of the driver. It is the shape of the
-- whole architecture, and almost every question about a USB environment --
-- where the monitor sits, what the sequencer may do, why the scoreboard sees
-- what it sees -- comes back to it.
--
-- THE TURNAROUND IS NOT OPTIONAL AND IT IS NOT ZERO
--
-- When the host finishes a token that requires a response, it stops driving
-- and the device starts. Between those two events there is a gap:
--
--     the host's drivers must actually release the line
--     the line must settle
--     the device must have decoded the token and decided to answer
--
-- Drive too EARLY and both sides are driving at once, which is contention:
-- the line is at an undefined level, both sides see corruption, and NEITHER
-- sees a cause. Drive too LATE and the host has already timed out.
--
--     start early  ->  contention, and no symptom that points at it
--     start late   ->  a timeout the host reports as "no response"
--
-- AN ENVIRONMENT THAT DOES NOT MODEL IT DEBUGS ITSELF
--
-- This is the practical consequence and it is worth stating bluntly. A
-- two-agent environment whose drivers do not respect a turnaround produces
-- contention that the real bus never would, on a schedule determined by
-- testbench scheduling rather than by the design. The failures are
-- intermittent, they move when you add a $display, and they look exactly
-- like a design bug.
--
-- Weeks go into that. The fix is a block like this one: ownership is a
-- MODELLED, CHECKED property of the environment, and a driver that drives
-- when it does not own the bus is a testbench error reported as such --
-- immediately, with a name, and distinguishable from the design's faults.
--
-- OWNERSHIP IS DERIVED, NOT NEGOTIATED
--
-- There is no arbitration on a USB bus. The host owns it by default and
-- hands it over IMPLICITLY, by sending a packet that requires a response.
-- The device never initiates anything.
--
-- So a device driving an idle bus is not "an agent that won arbitration
-- early". It is an agent doing something no real device can do, and the
-- environment must say so rather than letting it through and producing a
-- stimulus the design will never see in silicon.
library ieee;
use ieee.std_logic_1164.all;

package usb_bus_pkg is
  -- The five bus states. The two turnaround states are not decoration: they
  -- are the states in which NOBODY owns the wire, and an environment whose
  -- ownership model has no such state will let an agent drive into the gap.
  type bus_state_t is (B_IDLE, B_HOST, B_TURN_DEV, B_DEV, B_TURN_HST);
  type owner_t     is (O_NONE, O_HOST, O_DEV);
  type bus_err_t   is (E_NONE, E_CONTENTION, E_EARLY, E_LATE,
                       E_OVERHOLD, E_STOLEN);

  function bs_code (s : bus_state_t) return std_logic_vector;
  function ow_code (o : owner_t)     return std_logic_vector;
  function be_code (e : bus_err_t)   return std_logic_vector;
end package usb_bus_pkg;

package body usb_bus_pkg is
  function bs_code (s : bus_state_t) return std_logic_vector is
  begin
    case s is
      when B_IDLE     => return "000";
      when B_HOST     => return "001";
      when B_TURN_DEV => return "010";
      when B_DEV      => return "011";
      when B_TURN_HST => return "100";
    end case;
  end function;

  function ow_code (o : owner_t) return std_logic_vector is
  begin
    case o is
      when O_NONE => return "00";
      when O_HOST => return "01";
      when O_DEV  => return "10";
    end case;
  end function;

  function be_code (e : bus_err_t) return std_logic_vector is
  begin
    case e is
      when E_NONE       => return "000";
      when E_CONTENTION => return "001";
      when E_EARLY      => return "010";
      when E_LATE       => return "011";
      when E_OVERHOLD   => return "100";
      when E_STOLEN     => return "101";
    end case;
  end function;
end package body usb_bus_pkg;

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

entity usb_bus_ownership is
  generic (
    TA_MIN   : integer := 2;    -- cycles before the new owner may drive
    TA_MAX   : integer := 8;    -- cycles before the turnaround is dead
    HOLD_MAX : integer := 16    -- cycles an owner may drive continuously
  );
  port (
    clk          : in  std_logic;
    rst_n        : in  std_logic;

    host_drive   : in  std_logic;   -- the host agent is driving the wire
    dev_drive    : in  std_logic;   -- the device agent is driving the wire
    handover     : in  std_logic;   -- this packet requires a response
    rel_bus      : in  std_logic;   -- the current owner has finished
    eot          : in  std_logic;

    state        : out std_logic_vector(2 downto 0);
    owner        : out std_logic_vector(1 downto 0);
    ta_age       : out std_logic_vector(4 downto 0);
    hold_age     : out std_logic_vector(4 downto 0);
    grant_pulse  : out std_logic;
    err_pulse    : out std_logic;
    err_code     : out std_logic_vector(2 downto 0);

    n_grants     : out std_logic_vector(31 downto 0);
    n_handovers  : out std_logic_vector(31 downto 0);
    n_contention : out std_logic_vector(31 downto 0);
    n_early      : out std_logic_vector(31 downto 0);
    n_late       : out std_logic_vector(31 downto 0);
    n_overhold   : out std_logic_vector(31 downto 0);
    n_stolen     : out std_logic_vector(31 downto 0)
  );
end entity usb_bus_ownership;

architecture rtl of usb_bus_ownership is

  signal st_r : bus_state_t := B_IDLE;
  signal ec_r : bus_err_t   := E_NONE;
  signal ta_r, hd_r : unsigned(4 downto 0) := (others => '0');
  signal gr_r, er_r : std_logic := '0';

  -- ---- CONTENTION is checked FIRST, in every state, before anything else.
  --
  -- Two agents driving one wire is not a state-machine transition; it is a
  -- physical fault, and once it has happened nothing else observed that
  -- cycle means anything. Checking it inside the per-state logic -- which is
  -- the natural way to write this block -- means every state has to remember
  -- to check it, and one of them will not.
  signal both_drive : std_logic;

  -- 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_g, c_h, c_c : unsigned(31 downto 0) := (others => '0');
  signal c_e, c_l, c_o, c_s : unsigned(31 downto 0) := (others => '0');

begin

  both_drive <= host_drive and dev_drive;

  state    <= bs_code(st_r);
  owner    <= ow_code(O_HOST) when st_r = B_HOST
         else ow_code(O_DEV)  when st_r = B_DEV
         else ow_code(O_NONE);
  ta_age      <= std_logic_vector(ta_r);
  hold_age    <= std_logic_vector(hd_r);
  grant_pulse <= gr_r;
  err_pulse   <= er_r;
  err_code    <= be_code(ec_r);

  n_grants     <= std_logic_vector(c_g);
  n_handovers  <= std_logic_vector(c_h);
  n_contention <= std_logic_vector(c_c);
  n_early      <= std_logic_vector(c_e);
  n_late       <= std_logic_vector(c_l);
  n_overhold   <= std_logic_vector(c_o);
  n_stolen     <= std_logic_vector(c_s);

  process (clk, rst_n)
    variable ns  : bus_state_t;
    variable nec : bus_err_t;
    variable nta, nhd : unsigned(4 downto 0);
    variable ngr, ner, nhv : std_logic;
  begin
    if rst_n = '0' then
      st_r <= B_IDLE;
      ec_r <= E_NONE;
      ta_r <= (others => '0');
      hd_r <= (others => '0');
      gr_r <= '0'; er_r <= '0';
      c_g <= (others => '0'); c_h <= (others => '0');
      c_c <= (others => '0'); c_e <= (others => '0');
      c_l <= (others => '0'); c_o <= (others => '0');
      c_s <= (others => '0');
    elsif rising_edge(clk) then
      ns := st_r; nta := ta_r; nhd := hd_r; nec := E_NONE;
      ngr := '0'; ner := '0'; nhv := '0';

      if eot = '1' then
        -- End of test returns the wire to nobody. An agent still driving
        -- here is not reported as an error: the run is over, and the
        -- interesting report at this point is the counters.
        ns := B_IDLE; nta := (others => '0'); nhd := (others => '0');
      elsif both_drive = '1' then
        -- ---- CONTENTION. Both agents on the wire. ----
        --
        -- The line is at an undefined level, both sides will see corruption,
        -- and neither will see a cause. Reported here and nowhere else, so
        -- it cannot be missed by a state that forgot to look.
        ner := '1'; nec := E_CONTENTION;
        ns := B_IDLE; nta := (others => '0'); nhd := (others => '0');
      else
        case st_r is
          when B_IDLE =>
            nhd := (others => '0');
            nta := (others => '0');
            if dev_drive = '1' then
              -- ---- A device driving an idle bus. ----
              --
              -- There is no arbitration on this bus. The host owns it by
              -- default and the device never initiates, so this is an agent
              -- doing something no real device can do -- and letting it
              -- through produces a stimulus the design will never see.
              ner := '1'; nec := E_STOLEN;
            elsif host_drive = '1' then
              ns := B_HOST; ngr := '1'; nhd := to_unsigned(1, 5);
            end if;

          when B_HOST =>
            if dev_drive = '1' then
              ner := '1'; nec := E_STOLEN;
              ns := B_IDLE; nhd := (others => '0');
            elsif hd_r >= to_unsigned(HOLD_MAX, 5) then
              -- ---- Driving past the hold bound. ----
              --
              -- An agent that never stops is the worst of the failures here,
              -- because the other agent's response collides with it and the
              -- contention is attributed to whoever spoke second.
              ner := '1'; nec := E_OVERHOLD;
              ns := B_IDLE; nhd := (others => '0');
            elsif handover = '1' then
              -- The packet requires a response: ownership passes, through a
              -- turnaround rather than directly.
              ns := B_TURN_DEV;
              nta := (others => '0'); nhd := (others => '0'); nhv := '1';
            elsif rel_bus = '1' then
              ns := B_IDLE; nhd := (others => '0');
            elsif host_drive = '1' then
              nhd := hd_r + 1;
            end if;

          when B_TURN_DEV =>
            if host_drive = '1' then
              -- The host has handed over. Driving again now is contention
              -- waiting to happen.
              ner := '1'; nec := E_STOLEN;
              ns := B_IDLE; nta := (others => '0');
            elsif dev_drive = '1' and ta_r < to_unsigned(TA_MIN, 5) then
              -- ---- TOO EARLY. ----
              --
              -- The host's drivers have not released the line yet. This is
              -- the failure with no symptom that points at it: both sides
              -- see corruption and neither sees a cause.
              ner := '1'; nec := E_EARLY;
              ns := B_IDLE; nta := (others => '0');
            elsif dev_drive = '1' then
              ns := B_DEV; ngr := '1';
              nhd := to_unsigned(1, 5); nta := (others => '0');
            elsif ta_r >= to_unsigned(TA_MAX, 5) then
              -- ---- TOO LATE. The device never answered. ----
              ner := '1'; nec := E_LATE;
              ns := B_IDLE; nta := (others => '0');
            else
              nta := ta_r + 1;
            end if;

          when B_DEV =>
            if host_drive = '1' then
              ner := '1'; nec := E_STOLEN;
              ns := B_IDLE; nhd := (others => '0');
            elsif hd_r >= to_unsigned(HOLD_MAX, 5) then
              ner := '1'; nec := E_OVERHOLD;
              ns := B_IDLE; nhd := (others => '0');
            elsif rel_bus = '1' or handover = '1' then
              -- The device has finished. The wire goes back to the host, and
              -- it goes back through a turnaround for exactly the same
              -- reason it came the other way through one.
              ns := B_TURN_HST;
              nta := (others => '0'); nhd := (others => '0');
            elsif dev_drive = '1' then
              nhd := hd_r + 1;
            end if;

          when B_TURN_HST =>
            if dev_drive = '1' then
              ner := '1'; nec := E_STOLEN;
              ns := B_IDLE; nta := (others => '0');
            elsif host_drive = '1' and ta_r < to_unsigned(TA_MIN, 5) then
              ner := '1'; nec := E_EARLY;
              ns := B_IDLE; nta := (others => '0');
            elsif host_drive = '1' then
              ns := B_HOST; ngr := '1';
              nhd := to_unsigned(1, 5); nta := (others => '0');
            elsif ta_r >= to_unsigned(TA_MAX, 5) then
              -- The host not taking the wire back is not an error on the
              -- device's part, but it is a stalled bus, and it is reported
              -- for the same reason chapter 24.1's timeout is: nothing else
              -- in this block fires when NOTHING happens.
              ner := '1'; nec := E_LATE;
              ns := B_IDLE; nta := (others => '0');
            else
              nta := ta_r + 1;
            end if;
        end case;
      end if;

      st_r <= ns;
      ta_r <= nta;
      hd_r <= nhd;
      ec_r <= nec;
      gr_r <= ngr;
      er_r <= ner;

      if ngr = '1' then c_g <= c_g + 1; end if;
      if nhv = '1' then c_h <= c_h + 1; end if;

      -- The per-cause counters are driven by the SAME pulse as the error,
      -- so they sum to the error total by construction (chapter 23.4).
      if ner = '1' then
        case nec is
          when E_CONTENTION => c_c <= c_c + 1;
          when E_EARLY      => c_e <= c_e + 1;
          when E_LATE       => c_l <= c_l + 1;
          when E_OVERHOLD   => c_o <= c_o + 1;
          when E_STOLEN     => c_s <= c_s + 1;
          when others       => null;
        end case;
      end if;
    end if;
  end process;

end architecture rtl;

9. A Complete Ownership Cycle, and One Drive Into the Gap

Handover, turnaround, response, return — and an early drive that is not one

usb_bus_ownership — one full cycle, then a drive into the turnaround

10 cycles
A ten-cycle waveform. The host drives and is granted the wire, then asserts handover and the machine enters TURN_DEV. ta_age counts to TA_MIN, the device drives and is granted the wire. The device asserts rel_bus and the machine enters TURN_HST. At cycle 7 the host drives while ta_age is still zero, which is inside the turnaround, and err_pulse fires.the host owns the wirethe host owns the wireTA_MIN elapsed: the device may driveTA_MIN elapsed: the devicemay drivethe device owns itthe device owns itdrove into the gap: EARLYdrove into the gap: EARLYclkhost_drivedev_drivehandoverrel_busstateIDLEHOSTTDEVTDEVTDEVDEVTHSTTHSTIDLEIDLEta_age0001200000grant_pulseerr_pulset0t1t2t3t4t5t6t7t8t9
The host takes the wire and hands over; the turnaround runs for TA_MIN cycles before the device may answer. The device releases and the wire starts back. At cycle 7 the host drives while ta_age is still 0 — into the gap — and the error is reported rather than allowed to become contention nobody can explain. handover and rel_bus carry the two transfers of ownership.

Cycles 2 to 4 are the whole point of the block. Nobody owns the wire, ta_age is counting, and both agents must wait. An environment with no representation of that interval has nowhere to put it, and therefore does not have one.

10. The Testbenches

Two exhaustive sweeps — every bus state crossed with all 16 input combinations (80 pairs), and every turnaround age crossed with "the new owner drove" and "it did not" (18 pairs) — plus the checks that carry the chapter.

Both edges of the turnaround, at every age in the window:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        if (a < TA_MIN) begin
          check(n_early == b_e + 1,
                "the device drove before the host's drivers could have released the line, and the adapter allowed it -- this is the failure with no symptom that points at it, because both sides see corruption and neither sees a cause");
          check(n_grants == b_g, "an early drive was granted the wire");
        end else begin
          check(n_grants == b_g + 1,
                "the device drove after the full turnaround and was not granted the wire");
          check(n_early == b_e, "a legal handover was reported as early");
        end

Contention from every state, because the version of this block that checks it per-state will have missed one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      check(state === st2[2:0],
            "the sweep could not reach the bus state it meant to reach");
      b_c = n_contention;
      step(1'b1,1'b1,1'b0,1'b0,1'b0);
      check(n_contention == b_c + 1,
            "both agents drove the wire at once and it was not reported -- the line is at an undefined level, both sides see corruption, and neither sees a cause");

And the invariant that makes the turnaround a real thing rather than a name:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // ---- Nobody owns the wire during a turnaround. That is what a
      // ---- turnaround IS, and an environment whose ownership model says
      // ---- otherwise will let an agent drive into it.
      check(!(((m_st == B_TURN_DEV) || (m_st == B_TURN_HST))
              && (owner !== O_NONE)),
            "the wire is owned during a turnaround -- the whole point of the gap is that nobody owns it");

10.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Testbench for usb_bus_ownership (Verilog-2005).
//
// WHAT IS EXHAUSTIVE HERE
//
//   Every one of the five bus states crossed with all 16 combinations of
//   {host_drive, dev_drive, handover, rel_bus} = 80 pairs, each state
//   reached by real ownership transfers rather than forced.
//
//   And every turnaround age from 0 to TA_MAX crossed with "the new owner
//   drove" and "it did not" = 18 pairs, which is what puts a drive attempt
//   on every cycle of the window including both of its edges.
//
// THE TWO CHECKS THAT CARRY THE CHAPTER
//
//   BOTH EDGES OF THE TURNAROUND. Driving at TA_MIN-1 must fail as EARLY
//   and driving at TA_MIN must be granted. Checking only the first accepts
//   a bus that never hands over at all; checking only the second accepts a
//   bus with no turnaround.
//
//   CONTENTION IS CHECKED IN EVERY STATE. Both agents driving at once is a
//   physical fault, not a transition, and the sweep drives it from all five
//   states -- because the natural way to write this block is to check it
//   inside the per-state logic, where one state will forget.
`timescale 1ns/1ps
module tb_bo_v;

  localparam integer TA_MIN   = 2;
  localparam integer TA_MAX   = 8;
  localparam integer HOLD_MAX = 16;

  localparam [2:0] B_IDLE=3'd0, B_HOST=3'd1, B_TURN_DEV=3'd2,
                   B_DEV=3'd3, B_TURN_HST=3'd4;
  localparam [1:0] O_NONE=2'd0, O_HOST=2'd1, O_DEV=2'd2;
  localparam [2:0] E_NONE=3'd0, E_CONTENTION=3'd1, E_EARLY=3'd2,
                   E_LATE=3'd3, E_OVERHOLD=3'd4, E_STOLEN=3'd5;

  reg clk = 1'b0, rst_n = 1'b0;
  reg host_drive = 1'b0, dev_drive = 1'b0;
  reg handover = 1'b0, rel_bus = 1'b0, eot = 1'b0;

  wire [2:0] state, err_code;
  wire [1:0] owner;
  wire [4:0] ta_age, hold_age;
  wire grant_pulse, err_pulse;
  wire [31:0] n_grants, n_handovers, n_contention, n_early, n_late,
              n_overhold, n_stolen;

  usb_bus_ownership #(.TA_MIN(TA_MIN), .TA_MAX(TA_MAX), .HOLD_MAX(HOLD_MAX)) dut (
    .clk(clk), .rst_n(rst_n),
    .host_drive(host_drive), .dev_drive(dev_drive),
    .handover(handover), .rel_bus(rel_bus), .eot(eot),
    .state(state), .owner(owner), .ta_age(ta_age), .hold_age(hold_age),
    .grant_pulse(grant_pulse), .err_pulse(err_pulse), .err_code(err_code),
    .n_grants(n_grants), .n_handovers(n_handovers),
    .n_contention(n_contention), .n_early(n_early), .n_late(n_late),
    .n_overhold(n_overhold), .n_stolen(n_stolen)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0;
  task check(input cond, input [1023:0] msg);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 25)
          $display("FAIL @%0t: %0s | st=%0d own=%0d ta=%0d hd=%0d g=%b e=%b(%0d)",
                   $time, msg, state, owner, ta_age, hold_age,
                   grant_pulse, err_pulse, err_code);
      end
    end
  endtask

  // ------------------------------------------------------------------
  // The shadow bus. Written from the protocol, not copied from the design.
  // ------------------------------------------------------------------
  reg [2:0] m_st, m_ec;
  reg [4:0] m_ta, m_hd;
  reg       m_gr, m_er;
  integer   m_g, m_h, m_c, m_e, m_l, m_o, m_s;

  integer seen_si [0:79];       // 5 states x 16 input combinations
  integer seen_ta [0:17];       // (TA_MAX+1) x {the new owner drove, or not}
  integer n_si, n_ta_s, n_steps;

  task model_reset;
    integer j;
    begin
      m_st = B_IDLE; m_ec = E_NONE; m_ta = 5'd0; m_hd = 5'd0;
      m_gr = 1'b0; m_er = 1'b0;
      m_g = 0; m_h = 0; m_c = 0; m_e = 0; m_l = 0; m_o = 0; m_s = 0;
      for (j = 0; j < 80; j = j + 1) seen_si[j] = 0;
      for (j = 0; j < 18; j = j + 1) seen_ta[j] = 0;
      n_si = 0; n_ta_s = 0; n_steps = 0;
    end
  endtask

  integer idx;
  task step(input hd_i, input dd_i, input hv_i, input rb_i, input eo_i);
    reg [2:0] ns, nec;
    reg [4:0] nta, nhd;
    reg ngr, ner, nhv, bothd, newdrv;
    begin
      host_drive = hd_i; dev_drive = dd_i;
      handover = hv_i; rel_bus = rb_i; eot = eo_i;
      #1;

      check(state    === m_st, "state disagrees with the shadow bus");
      check(owner    === ((m_st == B_HOST) ? O_HOST
                        : (m_st == B_DEV)  ? O_DEV : O_NONE),
            "owner disagrees with the state it is derived from");
      check(ta_age   === m_ta, "ta_age disagrees -- the turnaround is not the length the model says");
      check(hold_age === m_hd, "hold_age disagrees");
      check(err_code === m_ec, "err_code disagrees");
      check(grant_pulse === m_gr, "the grant pulse disagrees");
      check(err_pulse   === m_er, "the error pulse disagrees");
      check(!(grant_pulse && err_pulse),
            "the wire was granted and reported faulty in the same cycle");
      check(ta_age   <= TA_MAX[4:0],
            "the turnaround ran past its bound -- a turnaround that never expires is a bus that can stall for ever");
      check(hold_age <= HOLD_MAX[4:0],
            "an owner drove past the hold bound without being reported");

      // ---- Nobody owns the wire during a turnaround. That is what a
      // ---- turnaround IS, and an environment whose ownership model says
      // ---- otherwise will let an agent drive into it.
      check(!(((m_st == B_TURN_DEV) || (m_st == B_TURN_HST))
              && (owner !== O_NONE)),
            "the wire is owned during a turnaround -- the whole point of the gap is that nobody owns it");

      idx = m_st * 16 + (hd_i ? 8 : 0) + (dd_i ? 4 : 0) + (hv_i ? 2 : 0) + (rb_i ? 1 : 0);
      if (idx < 80) begin
        if (seen_si[idx] == 0) begin seen_si[idx] = 1; n_si = n_si + 1; end
      end
      if ((m_st == B_TURN_DEV) || (m_st == B_TURN_HST)) begin
        newdrv = (m_st == B_TURN_DEV) ? dd_i : hd_i;
        idx = m_ta * 2 + (newdrv ? 1 : 0);
        if (idx < 18) begin
          if (seen_ta[idx] == 0) begin seen_ta[idx] = 1; n_ta_s = n_ta_s + 1; end
        end
      end
      n_steps = n_steps + 1;

      // ---- advance the shadow bus ----
      ns = m_st; nta = m_ta; nhd = m_hd; nec = E_NONE;
      ngr = 1'b0; ner = 1'b0; nhv = 1'b0;
      bothd = hd_i && dd_i;

      if (eo_i) begin
        ns = B_IDLE; nta = 5'd0; nhd = 5'd0;
      end else if (bothd) begin
        ner = 1'b1; nec = E_CONTENTION;
        ns = B_IDLE; nta = 5'd0; nhd = 5'd0;
      end else begin
        case (m_st)
          B_IDLE: begin
            nhd = 5'd0; nta = 5'd0;
            if (dd_i) begin ner = 1'b1; nec = E_STOLEN; end
            else if (hd_i) begin ns = B_HOST; ngr = 1'b1; nhd = 5'd1; end
          end
          B_HOST: begin
            if (dd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nhd = 5'd0;
            end else if (m_hd >= HOLD_MAX[4:0]) begin
              ner = 1'b1; nec = E_OVERHOLD; ns = B_IDLE; nhd = 5'd0;
            end else if (hv_i) begin
              ns = B_TURN_DEV; nta = 5'd0; nhd = 5'd0; nhv = 1'b1;
            end else if (rb_i) begin
              ns = B_IDLE; nhd = 5'd0;
            end else if (hd_i) begin
              nhd = m_hd + 5'd1;
            end
          end
          B_TURN_DEV: begin
            if (hd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nta = 5'd0;
            end else if (dd_i && (m_ta < TA_MIN[4:0])) begin
              ner = 1'b1; nec = E_EARLY; ns = B_IDLE; nta = 5'd0;
            end else if (dd_i) begin
              ns = B_DEV; ngr = 1'b1; nhd = 5'd1; nta = 5'd0;
            end else if (m_ta >= TA_MAX[4:0]) begin
              ner = 1'b1; nec = E_LATE; ns = B_IDLE; nta = 5'd0;
            end else begin
              nta = m_ta + 5'd1;
            end
          end
          B_DEV: begin
            if (hd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nhd = 5'd0;
            end else if (m_hd >= HOLD_MAX[4:0]) begin
              ner = 1'b1; nec = E_OVERHOLD; ns = B_IDLE; nhd = 5'd0;
            end else if (rb_i || hv_i) begin
              ns = B_TURN_HST; nta = 5'd0; nhd = 5'd0;
            end else if (dd_i) begin
              nhd = m_hd + 5'd1;
            end
          end
          default: begin   // B_TURN_HST
            if (dd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nta = 5'd0;
            end else if (hd_i && (m_ta < TA_MIN[4:0])) begin
              ner = 1'b1; nec = E_EARLY; ns = B_IDLE; nta = 5'd0;
            end else if (hd_i) begin
              ns = B_HOST; ngr = 1'b1; nhd = 5'd1; nta = 5'd0;
            end else if (m_ta >= TA_MAX[4:0]) begin
              ner = 1'b1; nec = E_LATE; ns = B_IDLE; nta = 5'd0;
            end else begin
              nta = m_ta + 5'd1;
            end
          end
        endcase
      end

      m_st = ns; m_ta = nta; m_hd = nhd; m_ec = nec;
      m_gr = ngr; m_er = ner;
      if (ngr) m_g = m_g + 1;
      if (nhv) m_h = m_h + 1;
      if (ner) begin
        case (nec)
          E_CONTENTION: m_c = m_c + 1;
          E_EARLY:      m_e = m_e + 1;
          E_LATE:       m_l = m_l + 1;
          E_OVERHOLD:   m_o = m_o + 1;
          E_STOLEN:     m_s = m_s + 1;
          default: ;
        endcase
      end

      @(posedge clk); #1;
      host_drive = 1'b0; dev_drive = 1'b0;
      handover = 1'b0; rel_bus = 1'b0; eot = 1'b0;
    end
  endtask

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

  // Return the wire to nobody the way the design provides for.
  task quiesce;
    begin
      step(1'b0,1'b0,1'b0,1'b0,1'b1);
      idle(1);
      check(state === B_IDLE, "the bus did not return to idle");
      check(owner === O_NONE, "somebody still owns an idle wire");
    end
  endtask

  integer k, b_c, b_e, b_l, b_o, b_s, b_g, b_h, st2, cb, a;

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

    // ---- Phase A: the state after reset ----
    check(state === B_IDLE, "reset did not land on an idle bus");
    check(owner === O_NONE, "reset left the wire owned");
    check(err_pulse === 1'b0, "reset reported a bus fault");

    // ---- Phase B: a complete, legal ownership cycle, repeatedly. ----
    //
    // host drives -> hands over -> turnaround -> device drives ->
    // releases -> turnaround -> host drives again. ZERO errors.
    for (k = 0; k < 60; k = k + 1) begin
      b_g = n_grants; b_h = n_handovers;
      b_c = n_contention + n_early + n_late + n_overhold + n_stolen;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);            // host takes the wire
      check(owner === O_HOST, "the host did not get an idle wire");
      step(1'b1,1'b0,1'b1,1'b0,1'b0);            // ...and hands over
      check(state === B_TURN_DEV, "handover did not enter the turnaround");
      idle(TA_MIN);                               // the gap
      step(1'b0,1'b1,1'b0,1'b0,1'b0);            // the device answers
      check(owner === O_DEV, "the device did not get the wire after the turnaround");
      step(1'b0,1'b1,1'b0,1'b1,1'b0);            // ...and releases
      check(state === B_TURN_HST, "release did not enter the return turnaround");
      idle(TA_MIN);
      step(1'b1,1'b0,1'b0,1'b0,1'b0);            // the host takes it back
      check(owner === O_HOST, "the host did not get the wire back");
      step(1'b1,1'b0,1'b0,1'b1,1'b0);            // and finishes
      check(n_grants == b_g + 3, "a legal ownership cycle did not produce three grants");
      check(n_handovers == b_h + 1, "the handover was not counted");
      check(n_contention + n_early + n_late + n_overhold + n_stolen == b_c,
            "a completely legal ownership cycle produced a bus fault -- an environment that flags legal traffic is an environment whose ownership model gets switched off");
      quiesce;
    end

    // ---- Phase C: BOTH EDGES OF THE TURNAROUND. ----
    //
    // Driving at TA_MIN-1 is contention waiting to happen; driving at
    // TA_MIN is the handover working. Checking only one of the two accepts
    // either a bus with no turnaround or a bus that never hands over.
    for (a = 0; a <= TA_MAX; a = a + 1) begin
      quiesce;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);            // into the turnaround
      idle(a);                                    // wait `a` cycles
      b_e = n_early; b_g = n_grants; b_l = n_late;
      if (a < TA_MAX) begin
        step(1'b0,1'b1,1'b0,1'b0,1'b0);          // the device drives
        if (a < TA_MIN) begin
          check(n_early == b_e + 1,
                "the device drove before the host's drivers could have released the line, and the adapter allowed it -- this is the failure with no symptom that points at it, because both sides see corruption and neither sees a cause");
          check(n_grants == b_g, "an early drive was granted the wire");
        end else begin
          check(n_grants == b_g + 1,
                "the device drove after the full turnaround and was not granted the wire");
          check(n_early == b_e, "a legal handover was reported as early");
        end
      end else begin
        idle(1);
        check(n_late == b_l + 1,
              "the turnaround expired with nobody taking the wire and nothing was reported -- a turnaround that never expires is a bus that can stall for ever");
      end
    end

    // ---- Phase D: CONTENTION, from every state. ----
    //
    // Two agents on one wire is a physical fault rather than a transition,
    // so it is driven from all five states -- because the natural way to
    // write this block is to check it inside the per-state logic, and one
    // state will forget.
    for (st2 = 0; st2 < 5; st2 = st2 + 1) begin
      quiesce;
      case (st2)
        0: ;
        1: step(1'b1,1'b0,1'b0,1'b0,1'b0);
        2: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                 step(1'b1,1'b0,1'b1,1'b0,1'b0); end
        3: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                 step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                 step(1'b0,1'b1,1'b0,1'b0,1'b0); end
        4: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                 step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                 step(1'b0,1'b1,1'b0,1'b0,1'b0);
                 step(1'b0,1'b1,1'b0,1'b1,1'b0); end
      endcase
      check(state === st2[2:0],
            "the sweep could not reach the bus state it meant to reach");
      b_c = n_contention;
      step(1'b1,1'b1,1'b0,1'b0,1'b0);
      check(n_contention == b_c + 1,
            "both agents drove the wire at once and it was not reported -- the line is at an undefined level, both sides see corruption, and neither sees a cause");
      check(state === B_IDLE, "contention did not return the wire to nobody");
    end

    // ---- Phase E: a device driving an idle bus. ----
    quiesce;
    b_s = n_stolen;
    step(1'b0,1'b1,1'b0,1'b0,1'b0);
    check(n_stolen == b_s + 1,
          "a device drove an idle wire and it was allowed -- there is no arbitration on this bus and no real device can do this, so the stimulus is one the design will never see");

    // ---- Phase F: driving past the hold bound, on BOTH agents. ----
    for (k = 0; k < 40; k = k + 1) begin
      quiesce;
      b_o = n_overhold;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      for (a = 0; a < HOLD_MAX + 2; a = a + 1) step(1'b1,1'b0,1'b0,1'b0,1'b0);
      check(n_overhold == b_o + 1,
            "an agent drove continuously past the hold bound and nothing was reported -- the other agent's response will collide with it and the contention will be blamed on whoever spoke second");

      // ...and the device side, which is a separate branch of the design
      // and therefore a separate thing to get wrong.
      quiesce;
      b_o = n_overhold;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);
      idle(TA_MIN);
      for (a = 0; a < HOLD_MAX + 3; a = a + 1) step(1'b0,1'b1,1'b0,1'b0,1'b0);
      check(n_overhold == b_o + 1,
            "the device drove continuously past the hold bound and nothing was reported");
    end

    // ---- Phase F2: the turnaround expires, in BOTH directions. ----
    //
    // Nothing else in this block fires when NOTHING happens, which is the
    // same argument as chapter 24.1's timeout: the bound on the turnaround
    // is the only rule here that can notice a bus that has simply stalled.
    for (k = 0; k < 40; k = k + 1) begin
      quiesce;
      b_l = n_late;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);          // handed over to the device
      idle(TA_MAX + 2);                         // ...which never answers
      check(n_late == b_l + 1,
            "the device never answered and the turnaround never expired -- a turnaround with no bound is a bus that can stall for ever, and nothing else in this block fires when nothing happens");

      quiesce;
      b_l = n_late;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);
      idle(TA_MIN);
      step(1'b0,1'b1,1'b0,1'b0,1'b0);
      step(1'b0,1'b1,1'b0,1'b1,1'b0);          // returning to the host
      idle(TA_MAX + 2);                         // ...which never takes it
      check(n_late == b_l + 1,
            "the host never took the wire back and the return turnaround never expired");
    end

    // ---- Phase G: EXHAUSTIVE. Every state x every input combination. ----
    for (st2 = 0; st2 < 5; st2 = st2 + 1) begin
      for (cb = 0; cb < 16; cb = cb + 1) begin
        quiesce;
        case (st2)
          0: ;
          1: step(1'b1,1'b0,1'b0,1'b0,1'b0);
          2: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                   step(1'b1,1'b0,1'b1,1'b0,1'b0); end
          3: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                   step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                   step(1'b0,1'b1,1'b0,1'b0,1'b0); end
          4: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                   step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                   step(1'b0,1'b1,1'b0,1'b0,1'b0);
                   step(1'b0,1'b1,1'b0,1'b1,1'b0); end
        endcase
        check(state === st2[2:0],
              "the sweep could not reach the bus state it meant to reach");
        step(cb[3], cb[2], cb[1], cb[0], 1'b0);
        step(cb[3], cb[2], cb[1], cb[0], 1'b0);
      end
    end

    // ---- Phase H: every turnaround age, with and without the new owner
    // ---- driving, so the window is swept rather than sampled.
    for (a = 0; a <= TA_MAX; a = a + 1) begin
      for (k = 0; k < 2; k = k + 1) begin
        quiesce;
        step(1'b1,1'b0,1'b0,1'b0,1'b0);
        step(1'b1,1'b0,1'b1,1'b0,1'b0);
        idle(a);
        step(1'b0, k[0], 1'b0, 1'b0, 1'b0);
        idle(1);
      end
    end

    // ---- Phase I: random ----
    for (k = 0; k < 34000; k = k + 1)
      step(($unsigned($random) % 100) < 34,
           ($unsigned($random) % 100) < 30,
           ($unsigned($random) % 100) < 18,
           ($unsigned($random) % 100) < 18,
           ($unsigned($random) % 1000) < 8);

    // ---- Phase J: and a clean ownership cycle afterwards, so the model is
    // ---- shown to still work rather than merely to have stopped.
    quiesce;
    b_g = n_grants;
    b_c = n_contention + n_early + n_late + n_overhold + n_stolen;
    for (k = 0; k < 80; k = k + 1) begin
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);
      idle(TA_MIN);
      step(1'b0,1'b1,1'b0,1'b0,1'b0);
      step(1'b0,1'b1,1'b0,1'b1,1'b0);
      idle(TA_MIN);
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b0,1'b1,1'b0);
      quiesce;
    end
    check(n_grants == b_g + 240, "the ownership model stopped granting the wire");
    check(n_contention + n_early + n_late + n_overhold + n_stolen == b_c,
          "clean ownership cycles after the random phase produced bus faults");

    // ---- Final agreement ----
    check(n_grants     === m_g[31:0], "n_grants disagrees with the model");
    check(n_handovers  === m_h[31:0], "n_handovers disagrees");
    check(n_contention === m_c[31:0], "n_contention disagrees");
    check(n_early      === m_e[31:0], "n_early disagrees");
    check(n_late       === m_l[31:0], "n_late disagrees");
    check(n_overhold   === m_o[31:0], "n_overhold disagrees");
    check(n_stolen     === m_s[31:0], "n_stolen disagrees");

    check(n_si == 80, "not every bus state was crossed with every input combination");
    check(n_ta_s == 18, "not every turnaround age was seen with and without the new owner driving");
    check(n_grants     > 32'd0, "the wire was never granted to anybody");
    check(n_handovers  > 32'd0, "ownership was never handed over");
    check(n_contention > 32'd0, "contention was never exercised");
    check(n_early      > 32'd0, "an early drive was never exercised");
    check(n_late       > 32'd0, "a turnaround never expired");
    check(n_overhold   > 32'd0, "the hold bound was never exceeded");
    check(n_stolen     > 32'd0, "a non-owner never drove the wire");

    $display("REACH state-x-input=%0d/80 turnaround-age=%0d/18 steps=%0d",
             n_si, n_ta_s, n_steps);
    $display("COUNTERS grants=%0d handovers=%0d contention=%0d early=%0d late=%0d overhold=%0d stolen=%0d",
             n_grants, n_handovers, n_contention, n_early, n_late,
             n_overhold, n_stolen);
    $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_bus_ownership (SystemVerilog).
//
// WHAT IS EXHAUSTIVE HERE
//
//   Every one of the five bus states crossed with all 16 combinations of
//   {host_drive, dev_drive, handover, rel_bus} = 80 pairs, each state
//   reached by real ownership transfers rather than forced.
//
//   And every turnaround age from 0 to TA_MAX crossed with "the new owner
//   drove" and "it did not" = 18 pairs, which is what puts a drive attempt
//   on every cycle of the window including both of its edges.
//
// THE TWO CHECKS THAT CARRY THE CHAPTER
//
//   BOTH EDGES OF THE TURNAROUND. Driving at TA_MIN-1 must fail as EARLY
//   and driving at TA_MIN must be granted. Checking only the first accepts
//   a bus that never hands over at all; checking only the second accepts a
//   bus with no turnaround.
//
//   CONTENTION IS CHECKED IN EVERY STATE. Both agents driving at once is a
//   physical fault, not a transition, and the sweep drives it from all five
//   states -- because the natural way to write this block is to check it
//   inside the per-state logic, where one state will forget.
`timescale 1ns/1ps
module tb_bo_sv;
  import usb_bus_pkg::*;

  localparam int TA_MIN   = 2;
  localparam int TA_MAX   = 8;
  localparam int HOLD_MAX = 16;

  logic clk = 1'b0, rst_n = 1'b0;
  logic host_drive = 1'b0, dev_drive = 1'b0;
  logic handover = 1'b0, rel_bus = 1'b0, eot = 1'b0;

  bus_state_e state;
  bus_err_e   err_code;
  owner_e     owner;
  logic [4:0] ta_age, hold_age;
  logic grant_pulse, err_pulse;
  logic [31:0] n_grants, n_handovers, n_contention, n_early, n_late,
               n_overhold, n_stolen;

  usb_bus_ownership #(.TA_MIN(TA_MIN), .TA_MAX(TA_MAX), .HOLD_MAX(HOLD_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 | st=%0d own=%0d ta=%0d hd=%0d g=%b e=%b(%0d)",
                   $time, msg, state, owner, ta_age, hold_age,
                   grant_pulse, err_pulse, err_code);
      end
    end
  endtask

  // ------------------------------------------------------------------
  // The shadow bus. Written from the protocol, not copied from the design.
  // ------------------------------------------------------------------
  bus_state_e m_st;
  bus_err_e   m_ec;
  logic [4:0] m_ta, m_hd;
  logic       m_gr, m_er;
  int         m_g, m_h, m_c, m_e, m_l, m_o, m_s;

  int seen_si [80];             // 5 states x 16 input combinations
  int seen_ta [18];             // (TA_MAX+1) x {the new owner drove, or not}
  int n_si, n_ta_s, n_steps;

  task automatic model_reset();
    int j;
    begin
      m_st = B_IDLE; m_ec = E_NONE; m_ta = 5'd0; m_hd = 5'd0;
      m_gr = 1'b0; m_er = 1'b0;
      m_g = 0; m_h = 0; m_c = 0; m_e = 0; m_l = 0; m_o = 0; m_s = 0;
      for (j = 0; j < 80; j++) seen_si[j] = 0;
      for (j = 0; j < 18; j++) seen_ta[j] = 0;
      n_si = 0; n_ta_s = 0; n_steps = 0;
    end
  endtask

  int idx;
  task automatic step(input logic hd_i, input logic dd_i, input logic hv_i,
                      input logic rb_i, input logic eo_i);
    bus_state_e ns;
    bus_err_e   nec;
    logic [4:0] nta, nhd;
    logic ngr, ner, nhv, bothd, newdrv;
    begin
      host_drive = hd_i; dev_drive = dd_i;
      handover = hv_i; rel_bus = rb_i; eot = eo_i;
      #1;

      check(state    === m_st, "state disagrees with the shadow bus");
      check(owner    === ((m_st == B_HOST) ? O_HOST
                        : (m_st == B_DEV)  ? O_DEV : O_NONE),
            "owner disagrees with the state it is derived from");
      check(ta_age   === m_ta, "ta_age disagrees -- the turnaround is not the length the model says");
      check(hold_age === m_hd, "hold_age disagrees");
      check(err_code === m_ec, "err_code disagrees");
      check(grant_pulse === m_gr, "the grant pulse disagrees");
      check(err_pulse   === m_er, "the error pulse disagrees");
      check(!(grant_pulse && err_pulse),
            "the wire was granted and reported faulty in the same cycle");
      check(ta_age   <= 5'(TA_MAX),
            "the turnaround ran past its bound -- a turnaround that never expires is a bus that can stall for ever");
      check(hold_age <= 5'(HOLD_MAX),
            "an owner drove past the hold bound without being reported");

      // ---- Nobody owns the wire during a turnaround. That is what a
      // ---- turnaround IS, and an environment whose ownership model says
      // ---- otherwise will let an agent drive into it.
      check(!(((m_st == B_TURN_DEV) || (m_st == B_TURN_HST))
              && (owner !== O_NONE)),
            "the wire is owned during a turnaround -- the whole point of the gap is that nobody owns it");

      idx = int'(m_st) * 16 + (hd_i ? 8 : 0) + (dd_i ? 4 : 0) + (hv_i ? 2 : 0) + (rb_i ? 1 : 0);
      if (idx < 80) begin
        if (seen_si[idx] == 0) begin seen_si[idx] = 1; n_si = n_si + 1; end
      end
      if ((m_st == B_TURN_DEV) || (m_st == B_TURN_HST)) begin
        newdrv = (m_st == B_TURN_DEV) ? dd_i : hd_i;
        idx = int'(m_ta) * 2 + (newdrv ? 1 : 0);
        if (idx < 18) begin
          if (seen_ta[idx] == 0) begin seen_ta[idx] = 1; n_ta_s = n_ta_s + 1; end
        end
      end
      n_steps++;

      // ---- advance the shadow bus ----
      ns = m_st; nta = m_ta; nhd = m_hd; nec = E_NONE;
      ngr = 1'b0; ner = 1'b0; nhv = 1'b0;
      bothd = hd_i && dd_i;

      if (eo_i) begin
        ns = B_IDLE; nta = 5'd0; nhd = 5'd0;
      end else if (bothd) begin
        ner = 1'b1; nec = E_CONTENTION;
        ns = B_IDLE; nta = 5'd0; nhd = 5'd0;
      end else begin
        case (m_st)
          B_IDLE: begin
            nhd = 5'd0; nta = 5'd0;
            if (dd_i) begin ner = 1'b1; nec = E_STOLEN; end
            else if (hd_i) begin ns = B_HOST; ngr = 1'b1; nhd = 5'd1; end
          end
          B_HOST: begin
            if (dd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nhd = 5'd0;
            end else if (m_hd >= 5'(HOLD_MAX)) begin
              ner = 1'b1; nec = E_OVERHOLD; ns = B_IDLE; nhd = 5'd0;
            end else if (hv_i) begin
              ns = B_TURN_DEV; nta = 5'd0; nhd = 5'd0; nhv = 1'b1;
            end else if (rb_i) begin
              ns = B_IDLE; nhd = 5'd0;
            end else if (hd_i) begin
              nhd = m_hd + 5'd1;
            end
          end
          B_TURN_DEV: begin
            if (hd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nta = 5'd0;
            end else if (dd_i && (m_ta < 5'(TA_MIN))) begin
              ner = 1'b1; nec = E_EARLY; ns = B_IDLE; nta = 5'd0;
            end else if (dd_i) begin
              ns = B_DEV; ngr = 1'b1; nhd = 5'd1; nta = 5'd0;
            end else if (m_ta >= 5'(TA_MAX)) begin
              ner = 1'b1; nec = E_LATE; ns = B_IDLE; nta = 5'd0;
            end else begin
              nta = m_ta + 5'd1;
            end
          end
          B_DEV: begin
            if (hd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nhd = 5'd0;
            end else if (m_hd >= 5'(HOLD_MAX)) begin
              ner = 1'b1; nec = E_OVERHOLD; ns = B_IDLE; nhd = 5'd0;
            end else if (rb_i || hv_i) begin
              ns = B_TURN_HST; nta = 5'd0; nhd = 5'd0;
            end else if (dd_i) begin
              nhd = m_hd + 5'd1;
            end
          end
          default: begin   // B_TURN_HST
            if (dd_i) begin
              ner = 1'b1; nec = E_STOLEN; ns = B_IDLE; nta = 5'd0;
            end else if (hd_i && (m_ta < 5'(TA_MIN))) begin
              ner = 1'b1; nec = E_EARLY; ns = B_IDLE; nta = 5'd0;
            end else if (hd_i) begin
              ns = B_HOST; ngr = 1'b1; nhd = 5'd1; nta = 5'd0;
            end else if (m_ta >= 5'(TA_MAX)) begin
              ner = 1'b1; nec = E_LATE; ns = B_IDLE; nta = 5'd0;
            end else begin
              nta = m_ta + 5'd1;
            end
          end
        endcase
      end

      m_st = ns; m_ta = nta; m_hd = nhd; m_ec = nec;
      m_gr = ngr; m_er = ner;
      if (ngr) m_g = m_g + 1;
      if (nhv) m_h = m_h + 1;
      if (ner) begin
        case (nec)
          E_CONTENTION: m_c = m_c + 1;
          E_EARLY:      m_e = m_e + 1;
          E_LATE:       m_l = m_l + 1;
          E_OVERHOLD:   m_o = m_o + 1;
          E_STOLEN:     m_s = m_s + 1;
          default: ;
        endcase
      end

      @(posedge clk); #1;
      host_drive = 1'b0; dev_drive = 1'b0;
      handover = 1'b0; rel_bus = 1'b0; eot = 1'b0;
    end
  endtask

  task automatic idle(input int n);
    repeat (n) step(1'b0,1'b0,1'b0,1'b0,1'b0);
  endtask

  // Return the wire to nobody the way the design provides for.
  task automatic quiesce();
    begin
      step(1'b0,1'b0,1'b0,1'b0,1'b1);
      idle(1);
      check(state === B_IDLE, "the bus did not return to idle");
      check(owner === O_NONE, "somebody still owns an idle wire");
    end
  endtask

  int k, b_c, b_e, b_l, b_o, b_s, b_g, b_h, st2, cb, a;

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

    // ---- Phase A: the state after reset ----
    check(state === B_IDLE, "reset did not land on an idle bus");
    check(owner === O_NONE, "reset left the wire owned");
    check(err_pulse === 1'b0, "reset reported a bus fault");

    // ---- Phase B: a complete, legal ownership cycle, repeatedly. ----
    //
    // host drives -> hands over -> turnaround -> device drives ->
    // releases -> turnaround -> host drives again. ZERO errors.
    for (k = 0; k < 60; k++) begin
      b_g = n_grants; b_h = n_handovers;
      b_c = n_contention + n_early + n_late + n_overhold + n_stolen;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);            // host takes the wire
      check(owner === O_HOST, "the host did not get an idle wire");
      step(1'b1,1'b0,1'b1,1'b0,1'b0);            // ...and hands over
      check(state === B_TURN_DEV, "handover did not enter the turnaround");
      idle(TA_MIN);                               // the gap
      step(1'b0,1'b1,1'b0,1'b0,1'b0);            // the device answers
      check(owner === O_DEV, "the device did not get the wire after the turnaround");
      step(1'b0,1'b1,1'b0,1'b1,1'b0);            // ...and releases
      check(state === B_TURN_HST, "release did not enter the return turnaround");
      idle(TA_MIN);
      step(1'b1,1'b0,1'b0,1'b0,1'b0);            // the host takes it back
      check(owner === O_HOST, "the host did not get the wire back");
      step(1'b1,1'b0,1'b0,1'b1,1'b0);            // and finishes
      check(n_grants == b_g + 3, "a legal ownership cycle did not produce three grants");
      check(n_handovers == b_h + 1, "the handover was not counted");
      check(n_contention + n_early + n_late + n_overhold + n_stolen == b_c,
            "a completely legal ownership cycle produced a bus fault -- an environment that flags legal traffic is an environment whose ownership model gets switched off");
      quiesce();
    end

    // ---- Phase C: BOTH EDGES OF THE TURNAROUND. ----
    //
    // Driving at TA_MIN-1 is contention waiting to happen; driving at
    // TA_MIN is the handover working. Checking only one of the two accepts
    // either a bus with no turnaround or a bus that never hands over.
    for (a = 0; a <= TA_MAX; a++) begin
      quiesce();
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);            // into the turnaround
      idle(a);                                    // wait `a` cycles
      b_e = n_early; b_g = n_grants; b_l = n_late;
      if (a < TA_MAX) begin
        step(1'b0,1'b1,1'b0,1'b0,1'b0);          // the device drives
        if (a < TA_MIN) begin
          check(n_early == b_e + 1,
                "the device drove before the host's drivers could have released the line, and the adapter allowed it -- this is the failure with no symptom that points at it, because both sides see corruption and neither sees a cause");
          check(n_grants == b_g, "an early drive was granted the wire");
        end else begin
          check(n_grants == b_g + 1,
                "the device drove after the full turnaround and was not granted the wire");
          check(n_early == b_e, "a legal handover was reported as early");
        end
      end else begin
        idle(1);
        check(n_late == b_l + 1,
              "the turnaround expired with nobody taking the wire and nothing was reported -- a turnaround that never expires is a bus that can stall for ever");
      end
    end

    // ---- Phase D: CONTENTION, from every state. ----
    //
    // Two agents on one wire is a physical fault rather than a transition,
    // so it is driven from all five states -- because the natural way to
    // write this block is to check it inside the per-state logic, and one
    // state will forget.
    for (st2 = 0; st2 < 5; st2++) begin
      quiesce();
      case (st2)
        0: ;
        1: step(1'b1,1'b0,1'b0,1'b0,1'b0);
        2: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                 step(1'b1,1'b0,1'b1,1'b0,1'b0); end
        3: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                 step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                 step(1'b0,1'b1,1'b0,1'b0,1'b0); end
        4: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                 step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                 step(1'b0,1'b1,1'b0,1'b0,1'b0);
                 step(1'b0,1'b1,1'b0,1'b1,1'b0); end
      endcase
      check(state === bus_state_e'(st2),
            "the sweep could not reach the bus state it meant to reach");
      b_c = n_contention;
      step(1'b1,1'b1,1'b0,1'b0,1'b0);
      check(n_contention == b_c + 1,
            "both agents drove the wire at once and it was not reported -- the line is at an undefined level, both sides see corruption, and neither sees a cause");
      check(state === B_IDLE, "contention did not return the wire to nobody");
    end

    // ---- Phase E: a device driving an idle bus. ----
    quiesce();
    b_s = n_stolen;
    step(1'b0,1'b1,1'b0,1'b0,1'b0);
    check(n_stolen == b_s + 1,
          "a device drove an idle wire and it was allowed -- there is no arbitration on this bus and no real device can do this, so the stimulus is one the design will never see");

    // ---- Phase F: driving past the hold bound, on BOTH agents. ----
    for (k = 0; k < 40; k++) begin
      quiesce();
      b_o = n_overhold;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      for (a = 0; a < HOLD_MAX + 2; a++) step(1'b1,1'b0,1'b0,1'b0,1'b0);
      check(n_overhold == b_o + 1,
            "an agent drove continuously past the hold bound and nothing was reported -- the other agent's response will collide with it and the contention will be blamed on whoever spoke second");

      // ...and the device side, which is a separate branch of the design
      // and therefore a separate thing to get wrong.
      quiesce();
      b_o = n_overhold;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);
      idle(TA_MIN);
      for (a = 0; a < HOLD_MAX + 3; a++) step(1'b0,1'b1,1'b0,1'b0,1'b0);
      check(n_overhold == b_o + 1,
            "the device drove continuously past the hold bound and nothing was reported");
    end

    // ---- Phase F2: the turnaround expires, in BOTH directions. ----
    //
    // Nothing else in this block fires when NOTHING happens, which is the
    // same argument as chapter 24.1's timeout: the bound on the turnaround
    // is the only rule here that can notice a bus that has simply stalled.
    for (k = 0; k < 40; k++) begin
      quiesce();
      b_l = n_late;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);          // handed over to the device
      idle(TA_MAX + 2);                         // ...which never answers
      check(n_late == b_l + 1,
            "the device never answered and the turnaround never expired -- a turnaround with no bound is a bus that can stall for ever, and nothing else in this block fires when nothing happens");

      quiesce();
      b_l = n_late;
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);
      idle(TA_MIN);
      step(1'b0,1'b1,1'b0,1'b0,1'b0);
      step(1'b0,1'b1,1'b0,1'b1,1'b0);          // returning to the host
      idle(TA_MAX + 2);                         // ...which never takes it
      check(n_late == b_l + 1,
            "the host never took the wire back and the return turnaround never expired");
    end

    // ---- Phase G: EXHAUSTIVE. Every state x every input combination. ----
    for (st2 = 0; st2 < 5; st2++) begin
      for (cb = 0; cb < 16; cb++) begin
        quiesce();
        case (st2)
          0: ;
          1: step(1'b1,1'b0,1'b0,1'b0,1'b0);
          2: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                   step(1'b1,1'b0,1'b1,1'b0,1'b0); end
          3: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                   step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                   step(1'b0,1'b1,1'b0,1'b0,1'b0); end
          4: begin step(1'b1,1'b0,1'b0,1'b0,1'b0);
                   step(1'b1,1'b0,1'b1,1'b0,1'b0); idle(TA_MIN);
                   step(1'b0,1'b1,1'b0,1'b0,1'b0);
                   step(1'b0,1'b1,1'b0,1'b1,1'b0); end
        endcase
        check(state === bus_state_e'(st2),
              "the sweep could not reach the bus state it meant to reach");
        step(1'(cb[3]), 1'(cb[2]), 1'(cb[1]), 1'(cb[0]), 1'b0);
        step(1'(cb[3]), 1'(cb[2]), 1'(cb[1]), 1'(cb[0]), 1'b0);
      end
    end

    // ---- Phase H: every turnaround age, with and without the new owner
    // ---- driving, so the window is swept rather than sampled.
    for (a = 0; a <= TA_MAX; a++) begin
      for (k = 0; k < 2; k++) begin
        quiesce();
        step(1'b1,1'b0,1'b0,1'b0,1'b0);
        step(1'b1,1'b0,1'b1,1'b0,1'b0);
        idle(a);
        step(1'b0, 1'(k[0]), 1'b0, 1'b0, 1'b0);
        idle(1);
      end
    end

    // ---- Phase I: random ----
    for (k = 0; k < 34000; k++)
      step($urandom_range(0,99) < 34,
           $urandom_range(0,99) < 30,
           $urandom_range(0,99) < 18,
           $urandom_range(0,99) < 18,
           $urandom_range(0,999) < 8);

    // ---- Phase J: and a clean ownership cycle afterwards, so the model is
    // ---- shown to still work rather than merely to have stopped.
    quiesce();
    b_g = n_grants;
    b_c = n_contention + n_early + n_late + n_overhold + n_stolen;
    for (k = 0; k < 80; k++) begin
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b1,1'b0,1'b0);
      idle(TA_MIN);
      step(1'b0,1'b1,1'b0,1'b0,1'b0);
      step(1'b0,1'b1,1'b0,1'b1,1'b0);
      idle(TA_MIN);
      step(1'b1,1'b0,1'b0,1'b0,1'b0);
      step(1'b1,1'b0,1'b0,1'b1,1'b0);
      quiesce();
    end
    check(n_grants == b_g + 240, "the ownership model stopped granting the wire");
    check(n_contention + n_early + n_late + n_overhold + n_stolen == b_c,
          "clean ownership cycles after the random phase produced bus faults");

    // ---- Final agreement ----
    check(n_grants     === 32'(m_g), "n_grants disagrees with the model");
    check(n_handovers  === 32'(m_h), "n_handovers disagrees");
    check(n_contention === 32'(m_c), "n_contention disagrees");
    check(n_early      === 32'(m_e), "n_early disagrees");
    check(n_late       === 32'(m_l), "n_late disagrees");
    check(n_overhold   === 32'(m_o), "n_overhold disagrees");
    check(n_stolen     === 32'(m_s), "n_stolen disagrees");

    check(n_si == 80, "not every bus state was crossed with every input combination");
    check(n_ta_s == 18, "not every turnaround age was seen with and without the new owner driving");
    check(n_grants     > 32'd0, "the wire was never granted to anybody");
    check(n_handovers  > 32'd0, "ownership was never handed over");
    check(n_contention > 32'd0, "contention was never exercised");
    check(n_early      > 32'd0, "an early drive was never exercised");
    check(n_late       > 32'd0, "a turnaround never expired");
    check(n_overhold   > 32'd0, "the hold bound was never exceeded");
    check(n_stolen     > 32'd0, "a non-owner never drove the wire");

    $display("REACH state-x-input=%0d/80 turnaround-age=%0d/18 steps=%0d",
             n_si, n_ta_s, n_steps);
    $display("COUNTERS grants=%0d handovers=%0d contention=%0d early=%0d late=%0d overhold=%0d stolen=%0d",
             n_grants, n_handovers, n_contention, n_early, n_late,
             n_overhold, n_stolen);
    $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_bus_ownership (VHDL-2008).
--
-- WHAT IS EXHAUSTIVE HERE
--
--   Every one of the five bus states crossed with all 16 combinations of
--   (host_drive, dev_drive, handover, rel_bus) = 80 pairs, each state
--   reached by real ownership transfers rather than forced.
--
--   And every turnaround age from 0 to TA_MAX crossed with "the new owner
--   drove" and "it did not" = 18 pairs, which is what puts a drive attempt
--   on every cycle of the window including both of its edges.
--
-- THE TWO CHECKS THAT CARRY THE CHAPTER
--
--   BOTH EDGES OF THE TURNAROUND. Driving at TA_MIN-1 must fail as EARLY
--   and driving at TA_MIN must be granted. Checking only the first accepts
--   a bus that never hands over at all; checking only the second accepts a
--   bus with no turnaround.
--
--   CONTENTION IS CHECKED IN EVERY STATE. Both agents driving at once is a
--   physical fault, not a transition, and the sweep drives it from all five
--   states -- because the natural way to write this block is to check it
--   inside the per-state logic, where one state will forget.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.usb_bus_pkg.all;

entity tb_bo_vhdl is
end entity tb_bo_vhdl;

architecture sim of tb_bo_vhdl is

  constant TA_MIN   : integer := 2;
  constant TA_MAX   : integer := 8;
  constant HOLD_MAX : integer := 16;

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

  signal host_drive, dev_drive : std_logic := '0';
  signal handover, rel_bus, eot : std_logic := '0';

  signal state, err_code : std_logic_vector(2 downto 0);
  signal owner : std_logic_vector(1 downto 0);
  signal ta_age, hold_age : std_logic_vector(4 downto 0);
  signal grant_pulse, err_pulse : std_logic;
  signal n_grants, n_handovers, n_contention : std_logic_vector(31 downto 0);
  signal n_early, n_late, n_overhold, n_stolen : std_logic_vector(31 downto 0);

begin

  dut : entity work.usb_bus_ownership
    generic map (TA_MIN => TA_MIN, TA_MAX => TA_MAX, HOLD_MAX => HOLD_MAX)
    port map (
      clk => clk, rst_n => rst_n,
      host_drive => host_drive, dev_drive => dev_drive,
      handover => handover, rel_bus => rel_bus, eot => eot,
      state => state, owner => owner, ta_age => ta_age, hold_age => hold_age,
      grant_pulse => grant_pulse, err_pulse => err_pulse, err_code => err_code,
      n_grants => n_grants, n_handovers => n_handovers,
      n_contention => n_contention, n_early => n_early, n_late => n_late,
      n_overhold => n_overhold, n_stolen => n_stolen
    );

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

  stim : process
    type si_arr is array (0 to 79) of integer;
    type ta_arr is array (0 to 17) of integer;

    variable errors, checks : integer := 0;

    -- ---- The shadow bus. Written from the protocol, not the design. ----
    variable m_st : bus_state_t := B_IDLE;
    variable m_ec : bus_err_t   := E_NONE;
    variable m_ta, m_hd : unsigned(4 downto 0) := (others => '0');
    variable m_gr, m_er : std_logic := '0';
    variable m_g, m_h, m_c, m_e, m_l, m_o, m_s : integer := 0;

    variable seen_si : si_arr := (others => 0);
    variable seen_ta : ta_arr := (others => 0);
    variable n_si, n_ta_s, n_steps : integer := 0;

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

    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_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 &
                 " | st=" & integer'image(bus_state_t'pos(m_st)) &
                 " ta=" & integer'image(to_integer(m_ta)) &
                 " hd=" & integer'image(to_integer(m_hd)) &
                 " ec=" & integer'image(bus_err_t'pos(m_ec))
            severity note;
        end if;
      end if;
    end procedure;

    function own_of (s : bus_state_t) return owner_t is
    begin
      if s = B_HOST then return O_HOST;
      elsif s = B_DEV then return O_DEV;
      else return O_NONE;
      end if;
    end function;

    procedure step (hd_i, dd_i, hv_i, rb_i, eo_i : std_logic) is
      variable nst  : bus_state_t;
      variable nec : bus_err_t;
      variable nta, nhd : unsigned(4 downto 0);
      variable ngr, ner, nhv, bothd, newdrv : std_logic;
      variable idx : integer;
    begin
      host_drive <= hd_i; dev_drive <= dd_i;
      handover <= hv_i; rel_bus <= rb_i; eot <= eo_i;
      wait for 1 ns;

      chk(state = bs_code(m_st), "state disagrees with the shadow bus");
      chk(owner = ow_code(own_of(m_st)),
          "owner disagrees with the state it is derived from");
      chk(unsigned(ta_age) = m_ta,
          "ta_age disagrees -- the turnaround is not the length the model says");
      chk(unsigned(hold_age) = m_hd, "hold_age disagrees");
      chk(err_code = be_code(m_ec), "err_code disagrees");
      chk(grant_pulse = m_gr, "the grant pulse disagrees");
      chk(err_pulse   = m_er, "the error pulse disagrees");
      chk(not (grant_pulse = '1' and err_pulse = '1'),
          "the wire was granted and reported faulty in the same cycle");
      chk(unsigned(ta_age) <= to_unsigned(TA_MAX, 5),
          "the turnaround ran past its bound -- a turnaround that never expires is a bus that can stall for ever");
      chk(unsigned(hold_age) <= to_unsigned(HOLD_MAX, 5),
          "an owner drove past the hold bound without being reported");

      -- ---- Nobody owns the wire during a turnaround. That is what a
      -- ---- turnaround IS, and an environment whose ownership model says
      -- ---- otherwise will let an agent drive into it.
      chk(not ((m_st = B_TURN_DEV or m_st = B_TURN_HST)
               and own_of(m_st) /= O_NONE),
          "the wire is owned during a turnaround -- the whole point of the gap is that nobody owns it");

      idx := bus_state_t'pos(m_st) * 16;
      if hd_i = '1' then idx := idx + 8; end if;
      if dd_i = '1' then idx := idx + 4; end if;
      if hv_i = '1' then idx := idx + 2; end if;
      if rb_i = '1' then idx := idx + 1; end if;
      if idx < 80 then
        if seen_si(idx) = 0 then seen_si(idx) := 1; n_si := n_si + 1; end if;
      end if;
      if m_st = B_TURN_DEV or m_st = B_TURN_HST then
        if m_st = B_TURN_DEV then newdrv := dd_i; else newdrv := hd_i; end if;
        idx := to_integer(m_ta) * 2;
        if newdrv = '1' then idx := idx + 1; end if;
        if idx < 18 then
          if seen_ta(idx) = 0 then seen_ta(idx) := 1; n_ta_s := n_ta_s + 1; end if;
        end if;
      end if;
      n_steps := n_steps + 1;

      -- ---- advance the shadow bus ----
      nst := m_st; nta := m_ta; nhd := m_hd; nec := E_NONE;
      ngr := '0'; ner := '0'; nhv := '0';
      bothd := hd_i and dd_i;

      if eo_i = '1' then
        nst := B_IDLE; nta := (others => '0'); nhd := (others => '0');
      elsif bothd = '1' then
        ner := '1'; nec := E_CONTENTION;
        nst := B_IDLE; nta := (others => '0'); nhd := (others => '0');
      else
        case m_st is
          when B_IDLE =>
            nhd := (others => '0'); nta := (others => '0');
            if dd_i = '1' then
              ner := '1'; nec := E_STOLEN;
            elsif hd_i = '1' then
              nst := B_HOST; ngr := '1'; nhd := to_unsigned(1, 5);
            end if;
          when B_HOST =>
            if dd_i = '1' then
              ner := '1'; nec := E_STOLEN; nst := B_IDLE; nhd := (others => '0');
            elsif m_hd >= to_unsigned(HOLD_MAX, 5) then
              ner := '1'; nec := E_OVERHOLD; nst := B_IDLE; nhd := (others => '0');
            elsif hv_i = '1' then
              nst := B_TURN_DEV; nta := (others => '0');
              nhd := (others => '0'); nhv := '1';
            elsif rb_i = '1' then
              nst := B_IDLE; nhd := (others => '0');
            elsif hd_i = '1' then
              nhd := m_hd + 1;
            end if;
          when B_TURN_DEV =>
            if hd_i = '1' then
              ner := '1'; nec := E_STOLEN; nst := B_IDLE; nta := (others => '0');
            elsif dd_i = '1' and m_ta < to_unsigned(TA_MIN, 5) then
              ner := '1'; nec := E_EARLY; nst := B_IDLE; nta := (others => '0');
            elsif dd_i = '1' then
              nst := B_DEV; ngr := '1';
              nhd := to_unsigned(1, 5); nta := (others => '0');
            elsif m_ta >= to_unsigned(TA_MAX, 5) then
              ner := '1'; nec := E_LATE; nst := B_IDLE; nta := (others => '0');
            else
              nta := m_ta + 1;
            end if;
          when B_DEV =>
            if hd_i = '1' then
              ner := '1'; nec := E_STOLEN; nst := B_IDLE; nhd := (others => '0');
            elsif m_hd >= to_unsigned(HOLD_MAX, 5) then
              ner := '1'; nec := E_OVERHOLD; nst := B_IDLE; nhd := (others => '0');
            elsif rb_i = '1' or hv_i = '1' then
              nst := B_TURN_HST; nta := (others => '0'); nhd := (others => '0');
            elsif dd_i = '1' then
              nhd := m_hd + 1;
            end if;
          when B_TURN_HST =>
            if dd_i = '1' then
              ner := '1'; nec := E_STOLEN; nst := B_IDLE; nta := (others => '0');
            elsif hd_i = '1' and m_ta < to_unsigned(TA_MIN, 5) then
              ner := '1'; nec := E_EARLY; nst := B_IDLE; nta := (others => '0');
            elsif hd_i = '1' then
              nst := B_HOST; ngr := '1';
              nhd := to_unsigned(1, 5); nta := (others => '0');
            elsif m_ta >= to_unsigned(TA_MAX, 5) then
              ner := '1'; nec := E_LATE; nst := B_IDLE; nta := (others => '0');
            else
              nta := m_ta + 1;
            end if;
        end case;
      end if;

      m_st := nst; m_ta := nta; m_hd := nhd; m_ec := nec;
      m_gr := ngr; m_er := ner;
      if ngr = '1' then m_g := m_g + 1; end if;
      if nhv = '1' then m_h := m_h + 1; end if;
      if ner = '1' then
        case nec is
          when E_CONTENTION => m_c := m_c + 1;
          when E_EARLY      => m_e := m_e + 1;
          when E_LATE       => m_l := m_l + 1;
          when E_OVERHOLD   => m_o := m_o + 1;
          when E_STOLEN     => m_s := m_s + 1;
          when others       => null;
        end case;
      end if;

      wait until rising_edge(clk);
      wait for 1 ns;
      host_drive <= '0'; dev_drive <= '0';
      handover <= '0'; rel_bus <= '0'; eot <= '0';
    end procedure;

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

    -- Return the wire to nobody the way the design provides for.
    procedure quiesce is
    begin
      step('0','0','0','0','1');
      idle(1);
      chk(state = bs_code(B_IDLE), "the bus did not return to idle");
      chk(owner = ow_code(O_NONE), "somebody still owns an idle wire");
    end procedure;

    -- Reach a bus state using real ownership transfers, never by forcing.
    procedure reach (s : integer) is
    begin
      case s is
        when 0 => null;
        when 1 => step('1','0','0','0','0');
        when 2 => step('1','0','0','0','0'); step('1','0','1','0','0');
        when 3 => step('1','0','0','0','0'); step('1','0','1','0','0');
                  idle(TA_MIN); step('0','1','0','0','0');
        when others => step('1','0','0','0','0'); step('1','0','1','0','0');
                       idle(TA_MIN); step('0','1','0','0','0');
                       step('0','1','0','1','0');
      end case;
    end procedure;

    variable b_c, b_e, b_l, b_o, b_s, b_g, b_h : integer := 0;
    variable hd_v, dd_v, hv_v, rb_v : std_logic;
    variable ln : line;

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

    -- ---- Phase A: the state after reset ----
    chk(state = bs_code(B_IDLE), "reset did not land on an idle bus");
    chk(owner = ow_code(O_NONE), "reset left the wire owned");
    chk(err_pulse = '0', "reset reported a bus fault");

    -- ---- Phase B: a complete, legal ownership cycle, repeatedly. ----
    for k in 0 to 59 loop
      b_g := to_integer(unsigned(n_grants));
      b_h := to_integer(unsigned(n_handovers));
      b_c := to_integer(unsigned(n_contention)) + to_integer(unsigned(n_early))
             + to_integer(unsigned(n_late)) + to_integer(unsigned(n_overhold))
             + to_integer(unsigned(n_stolen));
      step('1','0','0','0','0');                 -- host takes the wire
      chk(owner = ow_code(O_HOST), "the host did not get an idle wire");
      step('1','0','1','0','0');                 -- ...and hands over
      chk(state = bs_code(B_TURN_DEV), "handover did not enter the turnaround");
      idle(TA_MIN);                               -- the gap
      step('0','1','0','0','0');                 -- the device answers
      chk(owner = ow_code(O_DEV),
          "the device did not get the wire after the turnaround");
      step('0','1','0','1','0');                 -- ...and releases
      chk(state = bs_code(B_TURN_HST),
          "release did not enter the return turnaround");
      idle(TA_MIN);
      step('1','0','0','0','0');                 -- the host takes it back
      chk(owner = ow_code(O_HOST), "the host did not get the wire back");
      step('1','0','0','1','0');                 -- and finishes
      chk(to_integer(unsigned(n_grants)) = b_g + 3,
          "a legal ownership cycle did not produce three grants");
      chk(to_integer(unsigned(n_handovers)) = b_h + 1,
          "the handover was not counted");
      chk(to_integer(unsigned(n_contention)) + to_integer(unsigned(n_early))
          + to_integer(unsigned(n_late)) + to_integer(unsigned(n_overhold))
          + to_integer(unsigned(n_stolen)) = b_c,
          "a completely legal ownership cycle produced a bus fault -- an environment that flags legal traffic is an environment whose ownership model gets switched off");
      quiesce;
    end loop;

    -- ---- Phase C: BOTH EDGES OF THE TURNAROUND. ----
    for a in 0 to TA_MAX loop
      quiesce;
      step('1','0','0','0','0');
      step('1','0','1','0','0');                 -- into the turnaround
      idle(a);                                    -- wait `a` cycles
      b_e := to_integer(unsigned(n_early));
      b_g := to_integer(unsigned(n_grants));
      b_l := to_integer(unsigned(n_late));
      if a < TA_MAX then
        step('0','1','0','0','0');               -- the device drives
        if a < TA_MIN then
          chk(to_integer(unsigned(n_early)) = b_e + 1,
              "the device drove before the host's drivers could have released the line, and the adapter allowed it -- this is the failure with no symptom that points at it, because both sides see corruption and neither sees a cause");
          chk(to_integer(unsigned(n_grants)) = b_g,
              "an early drive was granted the wire");
        else
          chk(to_integer(unsigned(n_grants)) = b_g + 1,
              "the device drove after the full turnaround and was not granted the wire");
          chk(to_integer(unsigned(n_early)) = b_e,
              "a legal handover was reported as early");
        end if;
      else
        idle(1);
        chk(to_integer(unsigned(n_late)) = b_l + 1,
            "the turnaround expired with nobody taking the wire and nothing was reported -- a turnaround that never expires is a bus that can stall for ever");
      end if;
    end loop;

    -- ---- Phase D: CONTENTION, from every state. ----
    for st2 in 0 to 4 loop
      quiesce;
      reach(st2);
      chk(state = bs_code(bus_state_t'val(st2)),
          "the sweep could not reach the bus state it meant to reach");
      b_c := to_integer(unsigned(n_contention));
      step('1','1','0','0','0');
      chk(to_integer(unsigned(n_contention)) = b_c + 1,
          "both agents drove the wire at once and it was not reported -- the line is at an undefined level, both sides see corruption, and neither sees a cause");
      chk(state = bs_code(B_IDLE), "contention did not return the wire to nobody");
    end loop;

    -- ---- Phase E: a device driving an idle bus. ----
    quiesce;
    b_s := to_integer(unsigned(n_stolen));
    step('0','1','0','0','0');
    chk(to_integer(unsigned(n_stolen)) = b_s + 1,
        "a device drove an idle wire and it was allowed -- there is no arbitration on this bus and no real device can do this, so the stimulus is one the design will never see");

    -- ---- Phase F: driving past the hold bound, on BOTH agents. ----
    for k in 0 to 39 loop
      quiesce;
      b_o := to_integer(unsigned(n_overhold));
      step('1','0','0','0','0');
      for a in 0 to HOLD_MAX + 1 loop
        step('1','0','0','0','0');
      end loop;
      chk(to_integer(unsigned(n_overhold)) = b_o + 1,
          "an agent drove continuously past the hold bound and nothing was reported -- the other agent's response will collide with it and the contention will be blamed on whoever spoke second");

      quiesce;
      b_o := to_integer(unsigned(n_overhold));
      step('1','0','0','0','0');
      step('1','0','1','0','0');
      idle(TA_MIN);
      for a in 0 to HOLD_MAX + 2 loop
        step('0','1','0','0','0');
      end loop;
      chk(to_integer(unsigned(n_overhold)) = b_o + 1,
          "the device drove continuously past the hold bound and nothing was reported");
    end loop;

    -- ---- Phase F2: the turnaround expires, in BOTH directions. ----
    for k in 0 to 39 loop
      quiesce;
      b_l := to_integer(unsigned(n_late));
      step('1','0','0','0','0');
      step('1','0','1','0','0');                 -- handed over to the device
      idle(TA_MAX + 2);                           -- ...which never answers
      chk(to_integer(unsigned(n_late)) = b_l + 1,
          "the device never answered and the turnaround never expired -- a turnaround with no bound is a bus that can stall for ever, and nothing else in this block fires when nothing happens");

      quiesce;
      b_l := to_integer(unsigned(n_late));
      step('1','0','0','0','0');
      step('1','0','1','0','0');
      idle(TA_MIN);
      step('0','1','0','0','0');
      step('0','1','0','1','0');                 -- returning to the host
      idle(TA_MAX + 2);                           -- ...which never takes it
      chk(to_integer(unsigned(n_late)) = b_l + 1,
          "the host never took the wire back and the return turnaround never expired");
    end loop;

    -- ---- Phase G: EXHAUSTIVE. Every state x every input combination. ----
    for st2 in 0 to 4 loop
      for cb in 0 to 15 loop
        quiesce;
        reach(st2);
        chk(state = bs_code(bus_state_t'val(st2)),
            "the sweep could not reach the bus state it meant to reach");
        if (cb / 8) mod 2 = 1 then hd_v := '1'; else hd_v := '0'; end if;
        if (cb / 4) mod 2 = 1 then dd_v := '1'; else dd_v := '0'; end if;
        if (cb / 2) mod 2 = 1 then hv_v := '1'; else hv_v := '0'; end if;
        if  cb      mod 2 = 1 then rb_v := '1'; else rb_v := '0'; end if;
        step(hd_v, dd_v, hv_v, rb_v, '0');
        step(hd_v, dd_v, hv_v, rb_v, '0');
      end loop;
    end loop;

    -- ---- Phase H: every turnaround age, with and without the new owner
    -- ---- driving, so the window is swept rather than sampled.
    for a in 0 to TA_MAX loop
      for k in 0 to 1 loop
        quiesce;
        step('1','0','0','0','0');
        step('1','0','1','0','0');
        idle(a);
        if k = 1 then step('0','1','0','0','0'); else step('0','0','0','0','0'); end if;
        idle(1);
      end loop;
    end loop;

    -- ---- Phase I: random ----
    for k in 0 to 33999 loop
      hd_v := rnd_lt(34, 100);
      dd_v := rnd_lt(30, 100);
      hv_v := rnd_lt(18, 100);
      rb_v := rnd_lt(18, 100);
      step(hd_v, dd_v, hv_v, rb_v, rnd_lt(8, 1000));
    end loop;

    -- ---- Phase J: and a clean ownership cycle afterwards. ----
    quiesce;
    b_g := to_integer(unsigned(n_grants));
    b_c := to_integer(unsigned(n_contention)) + to_integer(unsigned(n_early))
           + to_integer(unsigned(n_late)) + to_integer(unsigned(n_overhold))
           + to_integer(unsigned(n_stolen));
    for k in 0 to 79 loop
      step('1','0','0','0','0');
      step('1','0','1','0','0');
      idle(TA_MIN);
      step('0','1','0','0','0');
      step('0','1','0','1','0');
      idle(TA_MIN);
      step('1','0','0','0','0');
      step('1','0','0','1','0');
      quiesce;
    end loop;
    chk(to_integer(unsigned(n_grants)) = b_g + 240,
        "the ownership model stopped granting the wire");
    chk(to_integer(unsigned(n_contention)) + to_integer(unsigned(n_early))
        + to_integer(unsigned(n_late)) + to_integer(unsigned(n_overhold))
        + to_integer(unsigned(n_stolen)) = b_c,
        "clean ownership cycles after the random phase produced bus faults");

    -- ---- Final agreement ----
    chk(to_integer(unsigned(n_grants))     = m_g, "n_grants disagrees with the model");
    chk(to_integer(unsigned(n_handovers))  = m_h, "n_handovers disagrees");
    chk(to_integer(unsigned(n_contention)) = m_c, "n_contention disagrees");
    chk(to_integer(unsigned(n_early))      = m_e, "n_early disagrees");
    chk(to_integer(unsigned(n_late))       = m_l, "n_late disagrees");
    chk(to_integer(unsigned(n_overhold))   = m_o, "n_overhold disagrees");
    chk(to_integer(unsigned(n_stolen))     = m_s, "n_stolen disagrees");

    chk(n_si = 80, "not every bus state was crossed with every input combination");
    chk(n_ta_s = 18, "not every turnaround age was seen with and without the new owner driving");
    chk(to_integer(unsigned(n_grants))     > 0, "the wire was never granted to anybody");
    chk(to_integer(unsigned(n_handovers))  > 0, "ownership was never handed over");
    chk(to_integer(unsigned(n_contention)) > 0, "contention was never exercised");
    chk(to_integer(unsigned(n_early))      > 0, "an early drive was never exercised");
    chk(to_integer(unsigned(n_late))       > 0, "a turnaround never expired");
    chk(to_integer(unsigned(n_overhold))   > 0, "the hold bound was never exceeded");
    chk(to_integer(unsigned(n_stolen))     > 0, "a non-owner never drove the wire");

    write(ln, string'("REACH state-x-input=") & integer'image(n_si) &
              "/80 turnaround-age=" & integer'image(n_ta_s) &
              "/18 steps=" & integer'image(n_steps));
    writeline(output, ln);
    write(ln, string'("COUNTERS grants=") & integer'image(to_integer(unsigned(n_grants))) &
              " handovers=" & integer'image(to_integer(unsigned(n_handovers))) &
              " contention=" & integer'image(to_integer(unsigned(n_contention))) &
              " early=" & integer'image(to_integer(unsigned(n_early))) &
              " late=" & integer'image(to_integer(unsigned(n_late))) &
              " overhold=" & integer'image(to_integer(unsigned(n_overhold))) &
              " stolen=" & integer'image(to_integer(unsigned(n_stolen))));
    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
bus state x input80 / 8080 / 8080 / 80
turnaround age x new owner drove18 / 1818 / 1818 / 18
Steps396393963939639
Checks executed437631437631437631
grants608861355219
handovers152915441149
contention354635545095
early drives (into the gap)352376191
turnarounds expired838387
hold bound exceeded808080
non-owner drove684167885225
ResultPASSPASSPASS

The 18-of-18 turnaround sweep is what puts a drive attempt on every cycle of the window, including both of its edges — which is the only way to distinguish a correct turnaround from one that is one cycle too short or one cycle too long.

12. Mutation Testing

#MutationVerilogSysVerVHDL
Y6the turnaround is skipped — ownership passes directly172761783110745
Y5a device driving an idle bus is allowed866686287002
Y7releasing the wire does not clear ownership769883886223
Y2the lower edge of the turnaround is not enforced501352662707
Y1contention is not detected355435625103
Y4an owner may drive for ever124412441244
Y3the turnaround never expires741741808
—unmutated baseline000

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

Y6 is the largest, and it is the mutation that describes an environment with no turnaround model at all: ownership passes straight from host to device with no gap. Every subsequent handover in the run is then wrong, which is why it dominates.

Y1 — contention undetected — scores only 3554, which is lower than several mutations that matter far less. That is not a measure of its importance; it is a measure of how often the suite drives contention, which is deliberately controlled because contention returns the bus to idle and disrupts everything after it. The directed phase that drives it from all five states is what actually kills it, and the score would be the same if the random phase were deleted.

13. Debugging Walkthrough: Three Weeks on a Testbench Bug

The report. A new USB device environment produces intermittent CRC failures. They appear in roughly one run in six, on different transfers each time, and they move when anything at all is changed — a $display, a different seed, a different simulator version.

Step 1 — is it the design? Dump the waveform. The device's transmitted packet is corrupt on the wire. The device's internal data is correct. So the corruption happens at the pin.

Step 2 — is it the PHY model? Replace it with a pass-through. No change.

Step 3 — look at the pin. Both drivers are enabled, for two cycles, at the start of the device's response. The host agent is still driving.

Step 4 — why? The host driver deasserts its enable in the clocking block's output skew after sending the last bit. The device driver asserts its enable as soon as its sequencer hands it an item. Those two events are ordered by the simulator's scheduler, not by anything in the protocol — which is why the failure moves when anything changes.

Step 5 — the environment had no turnaround. The host agent and the device agent each knew when they wanted to drive. Neither knew when the other had stopped, and nothing in the environment represented the interval between.

Step 6 — three weeks. Because every symptom pointed at the design: the packet on the wire was corrupt, the CRC failure was real, and the device's own logic was demonstrably correct. The one thing that would have pointed at the environment — "both agents are driving" — was not being checked by anything, because it is not a protocol violation. It is a physical impossibility, and physically impossible things are exactly what a testbench can do and a chip cannot.

14. The Environment This Block Belongs To

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The whole of Module 24, assembled. Five of these components are the ones
// built in chapters 24.1 to 24.5; the sixth is this chapter's ownership
// model, and it is the one that makes the other five trustworthy -- because
// a scoreboard fed by an environment that creates its own contention is a
// scoreboard reporting on the testbench.
class usb_env extends uvm_env;
  `uvm_component_utils(usb_env)

  // ---- TWO AGENTS, ONE WIRE. ----
  //
  // They are separate agents because they have separate sequencers, separate
  // stimulus and separate coverage. They are NOT separate interfaces: both
  // drive the same virtual interface, and the ownership model below is the
  // only thing that stops them doing it at the same time.
  usb_host_agent   host_agt;
  usb_device_agent dev_agt;

  // ---- ONE monitor, not two. ----
  //
  // There is one wire, so there is one thing to observe. A monitor per agent
  // sees what that agent DROVE, which is its intention -- and chapter 24.4
  // makes the same point about sampling coverage in the driver. What the
  // checkers need is what APPEARED, which only a bus monitor can give them.
  usb_bus_monitor  bus_mon;

  // ---- the ownership model: this chapter ----
  usb_bus_ownership_c  ownership;

  // ---- and the five components from the rest of the module ----
  usb_protocol_checker_c   checker;    // 24.1  was that legal?
  usb_liveness_c           liveness;   // 24.2  did it happen in time?
  usb_xfer_scoreboard      scoreboard; // 24.3  was that mine?
  usb_coverage_report      coverage;   // 24.4  what did we cover?
  usb_vip_comparator       vip_cmp;    // 24.5  does the VIP agree?

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

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    host_agt   = usb_host_agent::type_id::create("host_agt", this);
    dev_agt    = usb_device_agent::type_id::create("dev_agt", this);
    bus_mon    = usb_bus_monitor::type_id::create("bus_mon", this);
    ownership  = usb_bus_ownership_c::type_id::create("ownership", this);
    checker    = usb_protocol_checker_c::type_id::create("checker", this);
    liveness   = usb_liveness_c::type_id::create("liveness", this);
    scoreboard = usb_xfer_scoreboard::type_id::create("scoreboard", this);
    coverage   = usb_coverage_report::type_id::create("coverage", this);
    vip_cmp    = usb_vip_comparator::type_id::create("vip_cmp", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    // ---- Everything that CHECKS hangs off the ONE bus monitor. ----
    //
    // Not off the drivers. A checker fed by a driver is checking what the
    // testbench meant to do, which is the mistake chapter 24.1 forbids for
    // the protocol checker and chapter 24.4 forbids for coverage, for the
    // same reason in both cases.
    bus_mon.ap.connect(checker.ap);
    bus_mon.ap.connect(liveness.ap);
    bus_mon.ap.connect(coverage.ap);
    bus_mon.ap.connect(vip_cmp.dut_ap);

    // ---- The ownership model watches the DRIVE ENABLES, which is the one
    // ---- thing the bus monitor cannot see.
    //
    // A monitor sees the wire. It cannot distinguish "nobody is driving"
    // from "both are driving and the result happens to look like a 1". The
    // enables are testbench state, and contention is a testbench fault, so
    // this is the one connection in the environment that goes to the
    // drivers on purpose.
    host_agt.driver.drive_en_ap.connect(ownership.host_ap);
    dev_agt.driver.drive_en_ap.connect(ownership.dev_ap);

    // ---- The scoreboard needs both what was SENT and what was SEEN. ----
    host_agt.driver.issued_ap.connect(scoreboard.exp_ap);
    bus_mon.xfer_ap.connect(scoreboard.act_ap);
  endfunction

  // ---- The ownership model gates the drivers. ----
  //
  // This is the part that turns the model from a checker into a mechanism:
  // a driver asks before it drives, and is refused if it does not own the
  // wire. Without the gate the model reports contention; with it, the
  // contention does not happen and the report says which driver tried.
  function void end_of_elaboration_phase(uvm_phase phase);
    host_agt.driver.set_ownership_model(ownership);
    dev_agt.driver.set_ownership_model(ownership);
  endfunction
endclass

14.1 The driver side of the gate

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class usb_device_driver extends uvm_driver #(usb_pkt_item);
  `uvm_component_utils(usb_device_driver)

  virtual usb_if           vif;
  usb_bus_ownership_c      own;
  uvm_analysis_port #(bit) drive_en_ap;

  function void set_ownership_model(usb_bus_ownership_c m); own = m; endfunction

  task run_phase(uvm_phase phase);
    forever begin
      usb_pkt_item it;
      seq_item_port.get_next_item(it);

      // ---- ASK BEFORE DRIVING. ----
      //
      // Not "wait for the bus to look idle". Looking idle is exactly what a
      // wire does during the gap the host has not finished, and it is what
      // the three weeks in section 13 were spent on.
      own.request(OWNER_DEV);
      if (!own.granted(OWNER_DEV)) begin
        // A refusal is a TESTBENCH error and is reported as one. It is not
        // a design failure and must never be counted as one -- the whole
        // value of modelling ownership is that these two populations stop
        // being confused with each other.
        `uvm_error("BUS_OWN",
          $sformatf("the device driver tried to drive while the bus was %s -- this is a testbench fault, not a design failure, and letting it through produces contention that no real bus can create",
                    own.state_name()))
        seq_item_port.item_done();
        continue;
      end

      drive_en_ap.write(1'b1);
      drive_packet(it);
      drive_en_ap.write(1'b0);

      // ---- RELEASE, and do not drive again until the model says so. ----
      own.release_bus(OWNER_DEV);
      seq_item_port.item_done();
    end
  endtask

  task drive_packet(usb_pkt_item it);
    // ...the usual bit-level driving, unchanged by any of this.
  endtask
endclass

14.2 What the two agents may and may not randomise

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A device sequence cannot decide WHEN to speak. That is the protocol's
// decision, not the test's, and a sequence that randomises it is generating
// stimulus the design will never see.
class usb_device_response_seq extends uvm_sequence #(usb_pkt_item);
  `uvm_object_utils(usb_device_response_seq)
  function new(string name = "usb_device_response_seq"); super.new(name); endfunction

  task body();
    forever begin
      usb_pkt_item it = usb_pkt_item::type_id::create("it");

      // The device agent randomises WHAT it answers -- ACK, NAK, STALL,
      // data, a short packet -- and it randomises HOW LONG it takes within
      // the turnaround window. It does not randomise WHETHER to answer at
      // an arbitrary moment, because a real device cannot.
      start_item(it);
      if (!it.randomize() with {
            resp inside {RESP_ACK, RESP_NAK, RESP_STALL, RESP_DATA};
            resp dist {RESP_ACK := 70, RESP_NAK := 20,
                       RESP_STALL := 5, RESP_DATA := 5};
            // Within the window, including both of its edges, which is
            // where the interesting bugs are.
            turnaround_delay inside {[TA_MIN : TA_MAX-1]};
          })
        `uvm_error("RAND", "device response randomize failed")
      finish_item(it);
    end
  endtask
endclass

// ...and the error-injection sequence, which is allowed to break the rule --
// ONCE, deliberately, with the expectation written down.
//
// This is the sequence that must exist and must be separate. Folding
// turnaround violations into the normal response sequence means every test
// occasionally drives an illegal handover, and the environment's own
// ownership errors become background noise nobody reads.
class usb_turnaround_violation_seq extends uvm_sequence #(usb_pkt_item);
  `uvm_object_utils(usb_turnaround_violation_seq)
  function new(string name = "usb_turnaround_violation_seq"); super.new(name); endfunction

  task body();
    usb_pkt_item it = usb_pkt_item::type_id::create("it");
    start_item(it);
    // Deliberately inside the gap. The test that runs this sequence EXPECTS
    // an ownership error and fails if it does not get one -- which is the
    // only way to know the ownership model is still connected.
    if (!it.randomize() with { turnaround_delay == 0; })
      `uvm_error("RAND", "violation randomize failed")
    finish_item(it);
  endtask
endclass

15. Common Misconceptions

"Two agents means two interfaces." It means two drivers on one wire, and everything else follows from that.

"The turnaround is a timing detail." It is a state in which nobody owns the wire. An architecture with no such state cannot represent it.

"Wait for the bus to look idle." A wire in the middle of a turnaround looks idle. That is what makes this bug take three weeks.

"Contention is a protocol violation." No specification forbids it, because no real bus can produce it. It is a testbench fault, and nothing checks for it unless you decide to.

"Each agent needs its own monitor." A monitor per agent sees what that agent intended. The checkers need what appeared.

"The device agent randomises when it speaks." It randomises what it answers and how long it takes within the window. When is the protocol's decision.

"Error injection can live in the normal sequences." Then the environment's own ownership errors become background noise.

"Zero contention means the model is working." It also means the model is not instantiated. One test has to expect a violation.

"A failure that moves when you add a $display is a race in the design." The design does not know about your $display.

16. Exercises

1. Y4 scores 1244 in all three languages. Identify the directed phase responsible, then compute the probability that 34 000 cycles of the random phase produce seventeen consecutive drive cycles from one agent.

2. Contention is hoisted above the case statement. Write the per-state version, omit the check from exactly one state, and find which of the suite's checks catches it.

3. The turnaround sweep drives at every age 0 to TA_MAX. Show that checking only TA_MIN-1 and TA_MIN would pass a design whose window is [TA_MIN, TA_MIN] — granted at exactly one age and refused at every other.

4. own.request() gates the driver. Work out what the ownership model reports if the gate is removed but the model is left connected, and why that is still better than not having it.

5. Add a second device agent, as a hub would need. What changes in the ownership model, and which of the five error codes acquires a new meaning?

6. Write the test from §14.2 that fails if no ownership error is reported, and say why uvm_report_catcher is the wrong tool for it.

17. Summary

IdeaWhy it matters
Two agents, one wirethe shape of the whole architecture
The turnaround is a statenobody owns the wire, and it must be representable
Drive early → contentionundefined level, both sides corrupt, no cause visible
Drive late → a timeout the host blames on the device
Ownership is derived, not negotiatedthe host owns by default; the device never initiates
Contention is checked first, everywherefive states means five places to forget
A driver asks before driving"the bus looks idle" is exactly the bug
A refusal is a testbench errorand must never be counted as a design failure
One bus monitor, not one per agenta driver's monitor sees intentions
Error injection is a separate sequenceor the environment's own errors become noise
One test must expect a violationzero contention also means "not instantiated"
release is a Verilog keywordas is illegal_bins in SystemVerilog
80/80 state x input, 18/18 turnaround ages7 mutations, all killed in 3 languages

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o bo_v.out bo_v.v bo_v_tb.v && ./bo_v.out
SystemVerilogiverilog -g2012 -o bo_sv.out bo_sv.sv bo_sv_tb.sv && ./bo_sv.out
VHDL-2008 analysenvc --std=2008 -a bo_vhdl.vhd bo_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_bo_vhdl
VHDL-2008 runnvc --std=2008 -r tb_bo_vhdl
One mutationiverilog -g2005 -DMUT_Y1 -o mm bo_v_mut.v bo_v_tb.v && ./mm

All three implementations pass with 0 errors: every bus state crossed with every input combination, every turnaround age swept with and without the new owner driving, contention driven from all five states, and both edges of the turnaround window checked.


18. Module 24 in One Page

Six components, each answering a different question about the same bus:

ChapterComponentThe questionThe failure that defines it
24.1protocol checkerwas that legal?no timeout — silence violates no ordering rule
24.2assertion enginedid it happen in time?"eventually" has no failing case
24.3scoreboardwas that mine?matching on value lets two bugs cancel
24.4coverage modelwhat did we cover?unreachable and untested both read 0%
24.5VIP adapterdoes the second opinion agree?the shim that resolves the disagreement
24.6ownership modelwhose wire is it?no turnaround, so the env creates its own contention

Four habits run through all six, and they are worth more than any of the components:

Check the false-positive side. Every one of these components gets switched off if it cries wolf, and a component that is switched off has a false-negative rate of 100%. Three of the six chapters spend most of their stimulus proving the thing is quiet on legal traffic.

Name the cause, and make the causes sum. A single error counter is a number; per-cause counters driven by the same pulse are a diagnosis, and the fact that they add up is checkable.

Drain at the end of test. An unfinished obligation, an unmatched transfer, an uncovered bin and an unpaired event are all findings, and all four are reported as nothing by the default behaviour of the tool.

Know which half of your suite is doing the work. A mutation whose score is identical across three independently written testbenches is caught entirely by directed stimulus; one whose score varies is caught by the random half. Both are legitimate. Not knowing which is not.

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.