Skip to content
VLSI Mentor

USB · Module 29

USB Flash Drives

Every flash drive speaks Bulk-Only Transport — CBW out, data, CSW in. The spec enumerates thirteen cases of host-versus-device disagreement, six of them fatal, and the two rarest are the ones that ship broken.

The first case study. Modules 1 through 28 built USB from first principles and compared it with everything else; this module looks at what that machinery turns into in products people actually buy.

A flash drive is the simplest of them, and it is not simple.

1. A Flash Drive Is Three Bulk Transfers In A Loop

Strip away the enclosure, the NAND and the wear levelling and what is left on the wire is a loop of three transfers:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    CBW    31 bytes OUT   "here is a command, and here is how many bytes
                           I expect to move, in this direction"

    DATA   0..N bytes     the payload, if there is one

    CSW    13 bytes IN    "it worked / it did not, and I moved this many
                           fewer bytes than you asked for"

That is Bulk-Only Transport. Two bulk endpoints, no interrupt endpoint, no class-specific control requests beyond two, and a state machine small enough to fit on a page. It is the most widely deployed USB class protocol in existence by a wide margin.

That makes it an unusually good thing to build: a small, completely enumerable decision table where a single wrong cell is silent in normal use and unrecoverable when it fires.

2. The Thirteen Cases

Write the host's expectation as Hn (no data), Hi (device-to-host) or Ho (host-to-device), and the device's intent as Dn, Di or Do. Then every situation is one of these:

#hostdevicelengthoutcome
1HnDn—normal, residue 0
2HnDi—phase error
3HnDo—phase error
4HiDn—normal, residue = full, stall IN
5HiDiHi > Dinormal, residue = Hi − Di, stall IN
6HiDiHi = Dinormal, residue 0
7HiDiHi < Diphase error
8HiDo—phase error
9HoDn—normal, residue = full, stall OUT
10HoDoHo > Donormal, residue = Ho − Do, stall OUT
11HoDoHo = Donormal, residue 0
12HoDoHo < Dophase error
13HoDi—phase error

Six of the thirteen are fatal, and they fall into exactly two families:

Direction disagreement is always fatal — cases 8 and 13. The host is waiting on one endpoint and the device wants to fill the other. Nothing the transport can do makes those meet.

The host under-allocating is always fatal — cases 2, 3, 7 and 12. There is no way to move more bytes than the host set aside, and no way to tell it so except by declaring the phase lost. The residue field can only report a shortfall, never an excess.

Everything else is survivable, and the residue carries the difference.

3. The Design (Verilog-2005)

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  msc_bot_fsm -- Bulk-Only Transport, the protocol every USB flash
//  drive on earth actually speaks.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL.
//  This is NOT a mass-storage device. There is no SCSI command decoder,
//  no media, no FIFO and no error recovery beyond what the transport
//  itself defines. It is the TRANSPORT: the wrapper around every
//  command, and the arithmetic that decides what the host is told.
//
//  WHY THIS MECHANISM IS WORTH RTL
//  -------------------------------
//  A flash drive is three bulk transfers in a loop:
//
//      CBW   31 bytes OUT   "here is a command, and here is how many
//                            bytes I expect to move, in this direction"
//      DATA  0..N bytes     the payload, if any
//      CSW   13 bytes IN    "it worked / it did not, and I moved this
//                            many fewer bytes than you asked for"
//
//  The interesting part is the third line. The host states an
//  expectation in the CBW -- dCBWDataTransferLength and the direction
//  bit -- BEFORE the device has looked at the command. The device then
//  discovers what it actually wants to do. Those two can disagree, in
//  both length and direction, and the transport has to resolve every
//  combination without ever leaving the host and device out of step.
//
//  The BOT specification enumerates exactly THIRTEEN cases of that
//  disagreement. They are not a style guide; they are the contract, and
//  an implementation that gets case 7 wrong works perfectly against
//  every well-behaved host until somebody issues a command whose real
//  length exceeds what the host allocated -- and then the two ends
//  disagree about how many bytes are on the wire, which no amount of
//  retrying fixes.
//
//  That is why this is the mechanism worth building: it is a small,
//  completely enumerable decision table where a single wrong cell is
//  invisible in normal use and unrecoverable when it fires.
// =====================================================================
module msc_bot_fsm (
  input  wire        clk,
  input  wire        rst_n,

  // ---- the Command Block Wrapper, 31 bytes OUT ----
  input  wire        cbw_valid,
  input  wire [31:0] cbw_tag,       // dCBWTag: MUST come back in the CSW
  input  wire [31:0] cbw_len,       // dCBWDataTransferLength: the host's expectation
  input  wire        cbw_dir_in,    // bmCBWFlags bit 7: 1 = device-to-host
  input  wire        cbw_sig_ok,    // dCBWSignature == 'USBC'
  input  wire        cbw_cb_ok,     // bCBWCBLength in 1..16

  // ---- what the command layer says it will actually do ----
  //
  // Available only AFTER the CBW has been parsed, which is exactly why
  // the disagreement below is possible at all.
  input  wire        dev_none,      // this command moves no data
  input  wire        dev_dir_in,    // direction the device wants
  input  wire [31:0] dev_len,       // bytes the device will actually move
  input  wire        dev_fail,      // the command itself failed

  // ---- the Command Status Wrapper, 13 bytes IN ----
  output wire        csw_valid,
  output wire [31:0] csw_tag,       // echoed, always
  output wire [31:0] csw_residue,   // dCSWDataResidue = expected - actual
  output wire [1:0]  csw_status,    // 00 pass, 01 fail, 10 phase error

  // ---- what the endpoints are told to do ----
  output wire        stall_in,
  output wire        stall_out,
  output wire        need_reset,    // invalid CBW: wait for Reset Recovery

  output wire [2:0]  state,
  output wire [31:0] n_cbw,
  output wire [31:0] n_pass,
  output wire [31:0] n_fail,
  output wire [31:0] n_phase,
  output wire [31:0] n_stall,
  output wire [31:0] n_invalid
);

  localparam [1:0] ST_PASS  = 2'd0,
                   ST_FAIL  = 2'd1,
                   ST_PHASE = 2'd2;

  localparam [2:0] S_IDLE = 3'd0,   // waiting for a CBW
                   S_DATA = 3'd1,   // moving the data phase
                   S_CSW  = 3'd2,   // presenting the status
                   S_WEDGE = 3'd3;  // invalid CBW: both endpoints stalled

  // -------------------------------------------------------------------
  //  The host's stated expectation, as the spec's three kinds.
  // -------------------------------------------------------------------
  wire h_none = (cbw_len == 32'd0);
  wire h_in   = !h_none &&  cbw_dir_in;
  wire h_out  = !h_none && !cbw_dir_in;

  // The device's intent. dev_dir_in and dev_len are IGNORED when the
  // command moves no data -- a device that let a stale length leak into
  // a no-data command would report a residue for bytes that were never
  // going to move.
  wire d_none = dev_none;
  wire d_in   = !d_none &&  dev_dir_in;
  wire d_out  = !d_none && !dev_dir_in;
  // dev_len is read only through d_in / d_out below, both of which are
  // false when the command moves no data -- so no zeroing guard is needed
  // here, and adding one would be dead code. The bench asserts that
  // invariant rather than the design defending against it.
  wire [31:0] d_len = dev_len;

  // -------------------------------------------------------------------
  //  THE THIRTEEN CASES.
  //
  //  Direction disagreement is always fatal: cases 8 and 13. So is the
  //  host under-allocating, cases 2, 3, 7 and 12 -- there is no way to
  //  move more bytes than the host set aside, and no way to tell it so
  //  except by declaring the phase lost.
  //
  //  Everything else is survivable, and the residue carries the
  //  shortfall.
  // -------------------------------------------------------------------
  wire dir_clash = (h_in && d_out) || (h_out && d_in);          // 8, 13
  wire host_short = (h_none && !d_none)                         // 2, 3
                 || (h_in  && d_in  && (cbw_len < d_len))       // 7
                 || (h_out && d_out && (cbw_len < d_len));      // 12

  wire phase_err = dir_clash || host_short;

  // Bytes the transport will actually move.
  //
  // NO CLAMP TO cbw_len IS NEEDED, and that is worth stating because the
  // clamp is the obvious thing to write. Every situation in which the
  // device wants more bytes than the host allocated is ALREADY a phase
  // error: same-direction under-allocation is caught by host_short
  // (cases 7 and 12) and opposite-direction by dir_clash (cases 8 and 13).
  // So on any path that reaches this expression, d_len <= cbw_len holds.
  //
  // A clamp here would be unreachable code -- it was in the first version
  // of this file, and the mutation that broke it scored exactly ZERO,
  // which is how it was found. The invariant is asserted in the bench
  // instead, where a violation would be visible.
  //
  // There is no phase-error guard here either, for the same reason: both
  // consumers below (`residue` and the two short-data tests) already
  // exclude the phase-error case themselves. Guarding it a second time
  // scored zero as well. Two dead guards, both found by mutations that
  // refused to die, and both deleted rather than explained away.
  wire [31:0] moved = d_none ? 32'd0 : d_len;

  // dCSWDataResidue. On a phase error the field is defined but
  // meaningless to the host, which is about to reset the interface
  // anyway; it is reported as the full expectation so a trace reads
  // sensibly.
  wire [31:0] residue = phase_err ? cbw_len : (cbw_len - moved);

  // Cases 4 and 9: the host allocated a data phase and the device has
  // nothing to put in it. The transport must terminate that phase
  // rather than leave the host waiting, and a STALL is how BOT says so.
  wire short_in  = h_in  && !phase_err && (moved < cbw_len);
  wire short_out = h_out && !phase_err && (moved < cbw_len);

  // -------------------------------------------------------------------
  //  CBW validity. A CBW that is not 31 bytes, lacks the signature, or
  //  carries an out-of-range command length is not a CBW at all: the
  //  device stalls BOTH endpoints and waits for Reset Recovery. It must
  //  NOT answer with a CSW, because a CSW would imply it understood a
  //  command it did not.
  // -------------------------------------------------------------------
  wire cbw_ok = cbw_sig_ok && cbw_cb_ok;

  reg [2:0]  st;
  reg [31:0] tag_r, res_r;
  reg [1:0]  sts_r;
  reg        csw_r, sin_r, sout_r, wedge_r;

  reg [31:0] cbw_c, pass_c, fail_c, phase_c, stall_c, inval_c;

  assign state       = st;
  assign csw_valid   = csw_r;
  assign csw_tag     = tag_r;
  assign csw_residue = res_r;
  assign csw_status  = sts_r;
  assign stall_in    = sin_r;
  assign stall_out   = sout_r;
  assign need_reset  = wedge_r;
  assign n_cbw       = cbw_c;
  assign n_pass      = pass_c;
  assign n_fail      = fail_c;
  assign n_phase     = phase_c;
  assign n_stall     = stall_c;
  assign n_invalid   = inval_c;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st      <= S_IDLE;
      tag_r   <= 32'd0;
      res_r   <= 32'd0;
      sts_r   <= ST_PASS;
      csw_r   <= 1'b0;
      sin_r   <= 1'b0;
      sout_r  <= 1'b0;
      wedge_r <= 1'b0;
      cbw_c   <= 32'd0;
      pass_c  <= 32'd0;
      fail_c  <= 32'd0;
      phase_c <= 32'd0;
      stall_c <= 32'd0;
      inval_c <= 32'd0;
    end else begin
      csw_r  <= 1'b0;
      sin_r  <= 1'b0;
      sout_r <= 1'b0;

      case (st)
        S_IDLE: begin
          if (cbw_valid) begin
            cbw_c <= cbw_c + 32'd1;
            if (!cbw_ok) begin
              // Not a CBW. Stall both ways and wedge until Reset
              // Recovery -- answering anything else would be inventing
              // a reply to a command that was never received.
              st      <= S_WEDGE;
              wedge_r <= 1'b1;
              sin_r   <= 1'b1;
              sout_r  <= 1'b1;
              inval_c <= inval_c + 32'd1;
            end else begin
              // THE TAG IS CAPTURED HERE, from the CBW, and echoed
              // unchanged. A device that regenerates it, or echoes the
              // previous one, breaks every host's command queue.
              tag_r <= cbw_tag;
              res_r <= residue;
              sts_r <= phase_err ? ST_PHASE : (dev_fail ? ST_FAIL : ST_PASS);

              if (phase_err) begin
                phase_c <= phase_c + 32'd1;
                // A phase error stalls the direction the host was
                // waiting on, so the host stops waiting and comes to
                // read the CSW.
                if (h_in)  sin_r  <= 1'b1;
                if (h_out) sout_r <= 1'b1;
                if (h_in || h_out) stall_c <= stall_c + 32'd1;
                st <= S_CSW;
              end else begin
                if (dev_fail) fail_c <= fail_c + 32'd1;
                else          pass_c <= pass_c + 32'd1;
                // Cases 4 and 9: terminate a data phase the device
                // cannot fill.
                if (short_in)  begin sin_r  <= 1'b1; stall_c <= stall_c + 32'd1; end
                if (short_out) begin sout_r <= 1'b1; stall_c <= stall_c + 32'd1; end
                st <= (h_none && d_none) ? S_CSW : S_DATA;
              end
            end
          end
        end

        // The data phase itself is not modelled byte by byte; what the
        // transport owes the host is the LENGTH decision above, and that
        // is already fixed.
        S_DATA: st <= S_CSW;

        S_CSW: begin
          csw_r <= 1'b1;
          st    <= S_IDLE;
        end

        // Wedged. Only a reset gets out, which is what Reset Recovery
        // means: the host has to clear both stalls and start again.
        S_WEDGE: begin
          sin_r  <= 1'b1;
          sout_r <= 1'b1;
        end

        default: st <= S_IDLE;
      endcase
    end
  end

endmodule

Two guards that are not there, and why

The file carries no clamp on moved and no phase-error guard on it either. Both are the obvious things to write, and both would be dead code:

  • every case where the device wants more bytes than the host allocated is already a phase error, by host_short (cases 7, 12) or dir_clash (cases 8, 13), so d_len <= cbw_len holds everywhere moved is evaluated;
  • both consumers of moved — the residue and the two short-data tests — already exclude the phase-error case themselves.

Neither of those was reasoned out in advance. Each was a guard in the first version of this file, and each was found by a mutation that refused to die: breaking an unreachable expression changes nothing, so the score came back exactly zero. Section 9 tells that story properly, because the reasoning generalises further than the guards do.

The invariants the design now relies on are asserted in the testbench instead, where a violation would be visible.

One command, and the decision that resolves it

A Command Block Wrapper arrives stating a length and direction, the command layer discovers what the device actually wants, the two are compared against the thirteen enumerated cases, and the result is either a normal transfer carrying a residue or a phase error that stalls the endpoint and requires reset recoveryCBW, 31 B outstates N bytes +directionCommand layerwhat it really wantsComparethirteen enumeratedcasesCSW, 13 B intag + residue + statusPhase errorsix of the thirteenReset recoverythe only way outparseintentstatusorthen12
The comparison in the middle is the whole transport. Everything to its left is stated before the device has looked at the command; everything to its right is what the host is told afterwards.

4. The Same Command, Agreeing And Not

Case 6 and case 7 differ by one byte of expectation

Two Bulk-Only Transport commands. The first has the host expecting eight bytes and the device wanting eight, which completes with status PASS and residue zero. The second has the host expecting eight and the device wanting sixteen, which is spec case seven: the IN endpoint is stalled and the status is PHASE ERROR with the full residue.a transfer that agreesa transfer that agreesa transfer that cannota transfer that cannotcase 6: lengths agreecase 6: lengths agreePASS, residue 0PASS, residue 0case 7: device wants 16case 7: device wants 16PHASE ERROR, residue 8PHASE ERROR, residue 8clkcbw_validcbw_len8888888888dev_len8888161616161616stall_incsw_validcsw_statusnonenonePASSnonenonenonePHASEnonenonenoneresidue0000008000t0t1t2t3t4t5t6t7t8t9
Both CBWs ask for 8 bytes. In the first the device also wants 8 and the transfer completes with residue 0. In the second the device wants 16 — one more than the host allocated — and there is no legal way to proceed, so the IN endpoint is stalled and the status is PHASE ERROR.

The two differ by one number, and the difference between them is the difference between a working drive and a wedged one.

5. The Measurement

The directed phase is exhaustive over the whole decision space:

dimensionvalues
dCBWDataTransferLength0, 8, 16
direction bitIN, OUT
device intentnone, IN, OUT
device length0, 8, 16
command resultpass, fail

3 × 2 × 3 × 3 × 2 = 108 points, every one reachable. Device length and direction are swept even when the command declares no data, because ignoring them in that case is itself a property — a device that let a stale length leak into a no-data command would report a residue for bytes that were never going to move.

Directed phases only, identical in Verilog, SystemVerilog and VHDL:

stepschecksreacherrors
directed only2092,206108 / 1080

And every one of the thirteen cases is exercised, which the bench asserts rather than assumes:

case12345678910111213
times hit131513138703131375315

6. The Testbench (Verilog)

The model does not share the design's derivation. The design decides with two boolean expressions and derives everything from them; the model classifies into one of the thirteen named cases and looks the answer up in a table written straight from the specification. A boolean the design got subtly wrong cannot be got wrong the same way by a lookup keyed on a case number.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for msc_bot_fsm.
//
//  THE MODEL IS THE SPEC'S TABLE, NOT THE DESIGN'S BOOLEANS.
//
//  The design decides with two expressions -- `dir_clash` and
//  `host_short` -- and derives everything from them. The model does the
//  opposite: it classifies the situation into one of the thirteen NAMED
//  cases the Bulk-Only Transport specification enumerates, and then
//  looks the answer up in a table written straight from that document.
//
//  Two different derivations of the same contract. A boolean the design
//  got subtly wrong cannot be got wrong the same way by a lookup keyed
//  on a case number, which is the entire reason for writing it this way
//  rather than more carefully.
//
//  Every valid-gated output is captured at a DEFINED instant. The stall
//  decision is only meaningful in the cycle the CBW is consumed; reading
//  it later reads a scheduling artefact.
// =====================================================================
`timescale 1ns/1ps
module tb_bt_v;

  reg clk = 1'b0, rst_n = 1'b0;
  always #5 clk = ~clk;

  reg         cbw_valid = 1'b0;
  reg  [31:0] cbw_tag = 32'd0, cbw_len = 32'd0;
  reg         cbw_dir_in = 1'b0, cbw_sig_ok = 1'b1, cbw_cb_ok = 1'b1;
  reg         dev_none = 1'b0, dev_dir_in = 1'b0, dev_fail = 1'b0;
  reg  [31:0] dev_len = 32'd0;

  wire        csw_valid, stall_in, stall_out, need_reset;
  wire [31:0] csw_tag, csw_residue;
  wire [1:0]  csw_status;
  wire [2:0]  state;
  wire [31:0] n_cbw, n_pass, n_fail, n_phase, n_stall, n_invalid;

  msc_bot_fsm dut (
    .clk(clk), .rst_n(rst_n),
    .cbw_valid(cbw_valid), .cbw_tag(cbw_tag), .cbw_len(cbw_len),
    .cbw_dir_in(cbw_dir_in), .cbw_sig_ok(cbw_sig_ok), .cbw_cb_ok(cbw_cb_ok),
    .dev_none(dev_none), .dev_dir_in(dev_dir_in), .dev_len(dev_len),
    .dev_fail(dev_fail),
    .csw_valid(csw_valid), .csw_tag(csw_tag), .csw_residue(csw_residue),
    .csw_status(csw_status),
    .stall_in(stall_in), .stall_out(stall_out), .need_reset(need_reset),
    .state(state),
    .n_cbw(n_cbw), .n_pass(n_pass), .n_fail(n_fail), .n_phase(n_phase),
    .n_stall(n_stall), .n_invalid(n_invalid)
  );

  localparam [1:0] ST_PASS = 2'd0, ST_FAIL = 2'd1, ST_PHASE = 2'd2;

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

  // ---- cumulative across resets ----
  //
  // Every reset_dut zeroes the DUT's own counters, so a summary that read
  // them directly would report only whatever happened after the last one.
  // Per-step checks still use the DUT counters; these are for the totals.
  integer c_cbw = 0, c_pass = 0, c_fail = 0, c_phase = 0,
          c_stall = 0, c_invalid = 0;

  // $random is SIGNED: mask the sign bit before any modulo.
  function [31:0] urand;
    input dummy;
    begin urand = $random(seed) & 32'h3FFF_FFFF; end
  endfunction

  task ck(input cond, input [255:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step#%0d: %0s", $time, steps, what);
      end
    end
  endtask

  // ---- what the design said, sampled at a DEFINED instant ----
  reg        obs_sin, obs_sout, obs_wedge, obs_csw;
  reg [31:0] obs_tag, obs_res;
  reg [1:0]  obs_sts;

  // ---- how many times each of the thirteen cases was exercised ----
  integer case_hits [1:13];

  // -------------------------------------------------------------------
  //  THE MODEL: classify into the spec's thirteen cases.
  //
  //  h: 0 = Hn (no data), 1 = Hi (device-to-host), 2 = Ho (host-to-device)
  //  d: 0 = Dn (no data), 1 = Di (device-to-host), 2 = Do (host-to-device)
  // -------------------------------------------------------------------
  function [4:0] spec_case(input [1:0] h, input [1:0] d,
                           input [31:0] hl, input [31:0] dl);
    begin
      if      (h == 2'd0 && d == 2'd0)                spec_case = 5'd1;
      else if (h == 2'd0 && d == 2'd1)                spec_case = 5'd2;
      else if (h == 2'd0 && d == 2'd2)                spec_case = 5'd3;
      else if (h == 2'd1 && d == 2'd0)                spec_case = 5'd4;
      else if (h == 2'd1 && d == 2'd1 && hl >  dl)    spec_case = 5'd5;
      else if (h == 2'd1 && d == 2'd1 && hl == dl)    spec_case = 5'd6;
      else if (h == 2'd1 && d == 2'd1 && hl <  dl)    spec_case = 5'd7;
      else if (h == 2'd1 && d == 2'd2)                spec_case = 5'd8;
      else if (h == 2'd2 && d == 2'd0)                spec_case = 5'd9;
      else if (h == 2'd2 && d == 2'd2 && hl >  dl)    spec_case = 5'd10;
      else if (h == 2'd2 && d == 2'd2 && hl == dl)    spec_case = 5'd11;
      else if (h == 2'd2 && d == 2'd2 && hl <  dl)    spec_case = 5'd12;
      else                                            spec_case = 5'd13;
    end
  endfunction

  // Which cases are a phase error, straight from the specification.
  // Written as a set membership rather than as a condition, so it cannot
  // share an algebraic mistake with the design.
  function is_phase(input [4:0] c);
    begin
      is_phase = (c == 5'd2)  || (c == 5'd3)  || (c == 5'd7) ||
                 (c == 5'd8)  || (c == 5'd12) || (c == 5'd13);
    end
  endfunction

  // -------------------------------------------------------------------
  //  Drive one CBW and check everything the transport owes the host.
  // -------------------------------------------------------------------
  task run_cbw(input [31:0] tag, input [31:0] hlen, input hdir,
               input dnone, input ddir, input [31:0] dlen,
               input dfail, input sig_ok, input cb_ok);
    reg [1:0]  h, d;
    reg [4:0]  c;
    reg        e_phase, e_sin, e_sout;
    reg [31:0] e_moved, e_res;
    reg [1:0]  e_sts;
    reg [31:0] p0, f0, ph0, i0;
    integer    g;
    begin
      // classify, from the inputs only
      h = (hlen == 32'd0) ? 2'd0 : (hdir ? 2'd1 : 2'd2);
      d = dnone           ? 2'd0 : (ddir ? 2'd1 : 2'd2);
      c = spec_case(h, d, hlen, dnone ? 32'd0 : dlen);
      e_phase = is_phase(c);

      // the expected answer, from the case number
      e_moved = e_phase ? 32'd0
              : (d == 2'd0) ? 32'd0
              : ((dlen > hlen) ? hlen : dlen);
      e_res   = e_phase ? hlen : (hlen - e_moved);
      e_sts   = e_phase ? ST_PHASE : (dfail ? ST_FAIL : ST_PASS);
      // A short or absent data phase must be terminated on the side the
      // host is waiting on; a phase error stalls that side too.
      e_sin   = e_phase ? (h == 2'd1) : ((h == 2'd1) && (e_moved < hlen));
      e_sout  = e_phase ? (h == 2'd2) : ((h == 2'd2) && (e_moved < hlen));

      p0 = n_pass; f0 = n_fail; ph0 = n_phase; i0 = n_invalid;

      cbw_valid  = 1'b1;  cbw_tag = tag; cbw_len = hlen; cbw_dir_in = hdir;
      cbw_sig_ok = sig_ok; cbw_cb_ok = cb_ok;
      dev_none   = dnone; dev_dir_in = ddir; dev_len = dlen; dev_fail = dfail;

      @(posedge clk); #1;
      // ---- captured at the instant the CBW is consumed ----
      obs_sin   = stall_in;
      obs_sout  = stall_out;
      obs_wedge = need_reset;
      cbw_valid = 1'b0;

      if (!(sig_ok && cb_ok)) begin
        // ---- PROPERTY 1: an invalid CBW is not answered ----
        //
        // The device must stall both endpoints and wait for Reset
        // Recovery. Replying with a CSW would tell the host the command
        // was understood, and the host would believe it.
        ck(obs_sin && obs_sout,
           "an invalid CBW did not stall both endpoints");
        ck(obs_wedge, "an invalid CBW did not request reset recovery");
        ck(n_invalid == i0 + 32'd1, "an invalid CBW was not counted");
        c_cbw = c_cbw + 1; c_invalid = c_invalid + 1;
        // and no CSW, ever -- checked over a bounded window
        obs_csw = 1'b0;
        for (g = 0; g < 8; g = g + 1) begin
          @(posedge clk); #1;
          if (csw_valid) obs_csw = 1'b1;
        end
        ck(!obs_csw, "an invalid CBW was answered with a CSW");
        ck(n_pass == p0 && n_fail == f0 && n_phase == ph0,
           "an invalid CBW moved a status counter");
        // reset out of the wedge, which is what Reset Recovery does
        rst_n = 1'b0; @(posedge clk); @(posedge clk); rst_n = 1'b1;
        @(posedge clk); #1;
        steps = steps + 1;
      end else begin
        case_hits[c] = case_hits[c] + 1;
        c_cbw = c_cbw + 1;
        if (e_sts == ST_PHASE) c_phase = c_phase + 1;
        else if (e_sts == ST_FAIL) c_fail = c_fail + 1;
        else c_pass = c_pass + 1;
        if (e_sin)  c_stall = c_stall + 1;
        if (e_sout) c_stall = c_stall + 1;

        // ---- PROPERTY 2a: the invariants the design RELIES on ----
        //
        // The design carries no clamp and no zeroing guard, because two
        // invariants make both unnecessary. Relying on an invariant is
        // fine; relying on one nobody checks is not, so they are checked
        // here -- and a violation would be a bench bug before it was a
        // design bug.
        if (!e_phase && d != 2'd0)
          ck(dlen <= hlen,
             "INVARIANT: a non-phase-error transfer wants more than the host allocated");
        if (dnone)
          ck(e_moved == 32'd0,
             "INVARIANT: a no-data command moved a non-zero number of bytes");

        // ---- PROPERTY 2: the stall decision matches the case ----
        ck(obs_sin  === e_sin,  "stall_in disagrees with the spec case");
        ck(obs_sout === e_sout, "stall_out disagrees with the spec case");
        ck(!obs_wedge, "a valid CBW requested reset recovery");

        // ---- wait, bounded, for the CSW ----
        obs_csw = 1'b0;
        for (g = 0; g < 8 && !obs_csw; g = g + 1) begin
          @(posedge clk); #1;
          if (csw_valid) begin
            obs_csw = 1'b1;
            obs_tag = csw_tag;
            obs_res = csw_residue;
            obs_sts = csw_status;
          end
        end
        // ---- PROPERTY 3: exactly one CSW per valid CBW ----
        //
        // Silence would hang the host forever: it is waiting on an IN
        // endpoint that will never produce anything.
        ck(obs_csw, "a valid CBW produced no CSW");

        if (obs_csw) begin
          // ---- PROPERTY 4: THE TAG IS ECHOED UNCHANGED ----
          //
          // dCSWTag must equal dCBWTag. A device that regenerates it, or
          // returns the previous one, breaks every host that has more
          // than one command in flight -- and the corruption is silent,
          // because both values are plausible 32-bit numbers.
          ck(obs_tag === tag, "dCSWTag does not echo dCBWTag");
          // ---- PROPERTY 5: the status matches the case ----
          ck(obs_sts === e_sts, "csw_status disagrees with the spec case");
          // ---- PROPERTY 6: the residue is expected minus actual ----
          ck(obs_res === e_res, "dCSWDataResidue disagrees with the spec case");
          // ---- PROPERTY 7: a residue never exceeds the expectation ----
          //
          // A residue larger than dCBWDataTransferLength is arithmetically
          // impossible and would be read by the host as an enormous
          // negative transfer.
          ck(obs_res <= hlen, "the residue exceeds what the host asked for");
        end

        // ---- PROPERTY 8: exactly one status counter moved ----
        if (e_sts == ST_PHASE)
          ck(n_phase == ph0 + 32'd1 && n_pass == p0 && n_fail == f0,
             "counters wrong for a phase error");
        else if (e_sts == ST_FAIL)
          ck(n_fail == f0 + 32'd1 && n_pass == p0 && n_phase == ph0,
             "counters wrong for a failed command");
        else
          ck(n_pass == p0 + 32'd1 && n_fail == f0 && n_phase == ph0,
             "counters wrong for a passing command");

        ck(state === 3'd0, "the transport did not return to IDLE after a CSW");
        steps = steps + 1;
      end
    end
  endtask

  task reset_dut;
    begin
      rst_n = 1'b0; cbw_valid = 1'b0;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      @(posedge clk); #1;
    end
  endtask

  // ---- exhaustive reach ----
  //
  // hlen(3) x hdir(2) x dkind(3) x dlen(3) x dfail(2) = 108, every
  // combination an independent input. dlen and ddir are deliberately
  // swept even when the device declares no data, because IGNORING them
  // in that case is itself a property -- a device that let a stale
  // length leak into a no-data command would report a residue for bytes
  // that were never going to move.
  reg reach [0:107];
  integer nr, ri;

  integer hi_, hd, dk, dl_, df, k, idx;
  integer HLEN [0:2];
  integer DLEN [0:2];

  initial begin
    for (ri = 0; ri < 108; ri = ri + 1) reach[ri] = 1'b0;
    for (k = 1; k <= 13; k = k + 1) case_hits[k] = 0;
    HLEN[0] = 0; HLEN[1] = 8; HLEN[2] = 16;
    DLEN[0] = 0; DLEN[1] = 8; DLEN[2] = 16;
    seed = 32'd29001;

    reset_dut;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- the whole decision space.
    // =============================================================
    for (hi_ = 0; hi_ < 3; hi_ = hi_ + 1)
    for (hd  = 0; hd  < 2; hd  = hd + 1)
    for (dk  = 0; dk  < 3; dk  = dk + 1)
    for (dl_ = 0; dl_ < 3; dl_ = dl_ + 1)
    for (df  = 0; df  < 2; df  = df + 1) begin
      run_cbw(32'hA5A5_0000 + steps[31:0], HLEN[hi_][31:0], hd[0],
              (dk == 0), (dk == 1), DLEN[dl_][31:0], df[0], 1'b1, 1'b1);
      idx = ((((hi_ * 2 + hd) * 3 + dk) * 3 + dl_) * 2 + df);
      reach[idx] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED) -- every one of the thirteen cases, by name.
    //
    //  Phase 1 already reaches them all. This phase exists so the
    //  chapter can state that each NAMED case was exercised, and so a
    //  reader can find the one line that produces case 7.
    // =============================================================
    reset_dut;
    run_cbw(32'h0000_0001, 32'd0,  1'b0, 1'b1, 1'b0, 32'd0,  1'b0, 1'b1, 1'b1); // 1  Hn = Dn
    run_cbw(32'h0000_0002, 32'd0,  1'b0, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 2  Hn < Di
    run_cbw(32'h0000_0003, 32'd0,  1'b0, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 3  Hn < Do
    run_cbw(32'h0000_0004, 32'd16, 1'b1, 1'b1, 1'b0, 32'd0,  1'b0, 1'b1, 1'b1); // 4  Hi > Dn
    run_cbw(32'h0000_0005, 32'd16, 1'b1, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 5  Hi > Di
    run_cbw(32'h0000_0006, 32'd8,  1'b1, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 6  Hi = Di
    run_cbw(32'h0000_0007, 32'd8,  1'b1, 1'b0, 1'b1, 32'd16, 1'b0, 1'b1, 1'b1); // 7  Hi < Di
    run_cbw(32'h0000_0008, 32'd8,  1'b1, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 8  Hi <> Do
    run_cbw(32'h0000_0009, 32'd16, 1'b0, 1'b1, 1'b0, 32'd0,  1'b0, 1'b1, 1'b1); // 9  Ho > Dn
    run_cbw(32'h0000_000A, 32'd16, 1'b0, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 10 Ho > Do
    run_cbw(32'h0000_000B, 32'd8,  1'b0, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 11 Ho = Do
    run_cbw(32'h0000_000C, 32'd8,  1'b0, 1'b0, 1'b0, 32'd16, 1'b0, 1'b1, 1'b1); // 12 Ho < Do
    run_cbw(32'h0000_000D, 32'd8,  1'b0, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 13 Ho <> Di

    // =============================================================
    //  PHASE 3 (DIRECTED) -- the tag, over many distinct values.
    //
    //  The tag is the one field with no arithmetic in it, which makes it
    //  the easiest to get wrong in a way nothing else notices. Sixty-four
    //  distinct tags, including the ends of the range and values that a
    //  truncating implementation would alias together.
    // =============================================================
    reset_dut;
    for (k = 0; k < 64; k = k + 1) begin : tags
      reg [31:0] t;
      case (k % 4)
        0: t = 32'h0000_0000 + k;
        1: t = 32'hFFFF_FFFF - k;
        2: t = 32'h0000_FFFF + (k << 16);
        default: t = {k[7:0], ~k[7:0], k[7:0], ~k[7:0]};
      endcase
      run_cbw(t, 32'd8, 1'b1, 1'b0, 1'b1, 32'd8, 1'b0, 1'b1, 1'b1);
      ck(obs_tag === t, "a distinct tag was not echoed exactly");
    end

    // =============================================================
    //  PHASE 4 (DIRECTED, EXHAUSTIVE) -- CBW validity.
    //
    //  Signature and command length, all four combinations. Three of
    //  them are not a CBW at all, and the device must wedge rather than
    //  guess.
    // =============================================================
    //  Swept across every host length and direction as well, because the
    //  wedge must happen REGARDLESS of what the rest of the CBW claimed --
    //  the device has not understood the command and must not act on any
    //  part of it. 4 validity combinations x 3 lengths x 2 directions = 24,
    //  of which 18 are invalid.
    //
    //  The first version tested the four validity combinations once each,
    //  which gave the mutation that answers an invalid CBW a domain of
    //  three and a score of nine. Widening a property costs nothing and
    //  turns an uninformative number into one that means something.
    reset_dut;
    for (k = 0; k < 4; k = k + 1)
    for (hi_ = 0; hi_ < 3; hi_ = hi_ + 1)
    for (hd = 0; hd < 2; hd = hd + 1)
      run_cbw(32'hDEAD_0000 + k * 8 + hi_ * 2 + hd, HLEN[hi_][31:0], hd[0],
              1'b0, 1'b1, 32'd8, 1'b0, k[1], k[0]);

    // =============================================================
    //  PHASE 5 (RANDOM)
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 600; k = k + 1)
      run_cbw(urand(0), (urand(0) % 3) * 8, (urand(0) % 2),
              (urand(0) % 3) == 0, (urand(0) % 2), (urand(0) % 3) * 8,
              (urand(0) % 4) == 0, 1'b1, 1'b1);
`endif

    nr = 0; for (ri = 0; ri < 108; ri = ri + 1) if (reach[ri]) nr = nr + 1;

    $display("steps=%0d checks=%0d reach=%0d/108 errors=%0d",
             steps, checks, nr, errors);
    $display("[bot] cbws=%0d pass=%0d fail=%0d phase_error=%0d stalls=%0d invalid=%0d",
             c_cbw, c_pass, c_fail, c_phase, c_stall, c_invalid);
    $display("--- the thirteen Bulk-Only Transport cases, times exercised ---");
    $display("    case:  1    2    3    4    5    6    7    8    9   10   11   12   13");
    $write("    hits:");
    for (k = 1; k <= 13; k = k + 1) $write("%5d", case_hits[k]);
    $display("");
    for (k = 1; k <= 13; k = k + 1)
      ck(case_hits[k] > 0, "a Bulk-Only Transport case was never exercised");
    if (nr != 108) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

7. SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  msc_bot_fsm -- SystemVerilog.
//
//  Same hardware contract as the Verilog file: same ports, same widths,
//  same reset values, same cycle-by-cycle behaviour. The status and state
//  encodings become named types, and every continuous assignment is
//  written as `logic` + `assign` on separate lines.
//
//  THAT LAST POINT IS NOT COSMETIC. `logic x = expr;` is a one-shot
//  VARIABLE INITIALISER in SystemVerilog -- evaluated once at time zero
//  and never again -- while the Verilog `wire x = expr;` it came from is
//  a continuous assignment. Earlier in this track the mechanical
//  translation of five such lines produced 29,580 phantom failures
//  against a design that was entirely correct.
//
//  msc_bot_fsm -- Bulk-Only Transport, the protocol every USB flash
//  drive on earth actually speaks.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL.
//  This is NOT a mass-storage device. There is no SCSI command decoder,
//  no media, no FIFO and no error recovery beyond what the transport
//  itself defines. It is the TRANSPORT: the wrapper around every
//  command, and the arithmetic that decides what the host is told.
//
//  WHY THIS MECHANISM IS WORTH RTL
//  -------------------------------
//  A flash drive is three bulk transfers in a loop:
//
//      CBW   31 bytes OUT   "here is a command, and here is how many
//                            bytes I expect to move, in this direction"
//      DATA  0..N bytes     the payload, if any
//      CSW   13 bytes IN    "it worked / it did not, and I moved this
//                            many fewer bytes than you asked for"
//
//  The interesting part is the third line. The host states an
//  expectation in the CBW -- dCBWDataTransferLength and the direction
//  bit -- BEFORE the device has looked at the command. The device then
//  discovers what it actually wants to do. Those two can disagree, in
//  both length and direction, and the transport has to resolve every
//  combination without ever leaving the host and device out of step.
//
//  The BOT specification enumerates exactly THIRTEEN cases of that
//  disagreement. They are not a style guide; they are the contract, and
//  an implementation that gets case 7 wrong works perfectly against
//  every well-behaved host until somebody issues a command whose real
//  length exceeds what the host allocated -- and then the two ends
//  disagree about how many bytes are on the wire, which no amount of
//  retrying fixes.
//
//  That is why this is the mechanism worth building: it is a small,
//  completely enumerable decision table where a single wrong cell is
//  invisible in normal use and unrecoverable when it fires.
// =====================================================================
module msc_bot_fsm (
  input  logic        clk,
  input  logic        rst_n,

  // ---- the Command Block Wrapper, 31 bytes OUT ----
  input  logic        cbw_valid,
  input  logic [31:0] cbw_tag,       // dCBWTag: MUST come back in the CSW
  input  logic [31:0] cbw_len,       // dCBWDataTransferLength: the host's expectation
  input  logic        cbw_dir_in,    // bmCBWFlags bit 7: 1 = device-to-host
  input  logic        cbw_sig_ok,    // dCBWSignature == 'USBC'
  input  logic        cbw_cb_ok,     // bCBWCBLength in 1..16

  // ---- what the command layer says it will actually do ----
  //
  // Available only AFTER the CBW has been parsed, which is exactly why
  // the disagreement below is possible at all.
  input  logic        dev_none,      // this command moves no data
  input  logic        dev_dir_in,    // direction the device wants
  input  logic [31:0] dev_len,       // bytes the device will actually move
  input  logic        dev_fail,      // the command itself failed

  // ---- the Command Status Wrapper, 13 bytes IN ----
  output logic        csw_valid,
  output logic [31:0] csw_tag,       // echoed, always
  output logic [31:0] csw_residue,   // dCSWDataResidue = expected - actual
  output logic [1:0]  csw_status,    // 00 pass, 01 fail, 10 phase error

  // ---- what the endpoints are told to do ----
  output logic        stall_in,
  output logic        stall_out,
  output logic        need_reset,    // invalid CBW: wait for Reset Recovery

  output logic [2:0]  state,
  output logic [31:0] n_cbw,
  output logic [31:0] n_pass,
  output logic [31:0] n_fail,
  output logic [31:0] n_phase,
  output logic [31:0] n_stall,
  output logic [31:0] n_invalid
);

  localparam [1:0] ST_PASS  = 2'd0,
                   ST_FAIL  = 2'd1,
                   ST_PHASE = 2'd2;

  localparam [2:0] S_IDLE = 3'd0,   // waiting for a CBW
                   S_DATA = 3'd1,   // moving the data phase
                   S_CSW  = 3'd2,   // presenting the status
                   S_WEDGE = 3'd3;  // invalid CBW: both endpoints stalled

  // -------------------------------------------------------------------
  //  The host's stated expectation, as the spec's three kinds.
  // -------------------------------------------------------------------
  logic h_none;
  assign h_none = (cbw_len == 32'd0);
  logic h_in;
  assign h_in = !h_none &&  cbw_dir_in;
  logic h_out;
  assign h_out = !h_none && !cbw_dir_in;

  // The device's intent. dev_dir_in and dev_len are IGNORED when the
  // command moves no data -- a device that let a stale length leak into
  // a no-data command would report a residue for bytes that were never
  // going to move.
  logic d_none;
  assign d_none = dev_none;
  logic d_in;
  assign d_in = !d_none &&  dev_dir_in;
  logic d_out;
  assign d_out = !d_none && !dev_dir_in;
  // dev_len is read only through d_in / d_out below, both of which are
  // false when the command moves no data -- so no zeroing guard is needed
  // here, and adding one would be dead code. The bench asserts that
  // invariant rather than the design defending against it.
  logic [31:0] d_len;
  assign d_len = dev_len;

  // -------------------------------------------------------------------
  //  THE THIRTEEN CASES.
  //
  //  Direction disagreement is always fatal: cases 8 and 13. So is the
  //  host under-allocating, cases 2, 3, 7 and 12 -- there is no way to
  //  move more bytes than the host set aside, and no way to tell it so
  //  except by declaring the phase lost.
  //
  //  Everything else is survivable, and the residue carries the
  //  shortfall.
  // -------------------------------------------------------------------
  logic dir_clash;
  assign dir_clash = (h_in && d_out) || (h_out && d_in);      // 8, 13
  logic host_short;
  assign host_short = (h_none && !d_none)                       // 2, 3
                   || (h_in  && d_in  && (cbw_len < d_len))     // 7
                   || (h_out && d_out && (cbw_len < d_len));    // 12

  logic phase_err;
  assign phase_err = dir_clash || host_short;

  // Bytes the transport will actually move.
  //
  // NO CLAMP TO cbw_len IS NEEDED, and that is worth stating because the
  // clamp is the obvious thing to write. Every situation in which the
  // device wants more bytes than the host allocated is ALREADY a phase
  // error: same-direction under-allocation is caught by host_short
  // (cases 7 and 12) and opposite-direction by dir_clash (cases 8 and 13).
  // So on any path that reaches this expression, d_len <= cbw_len holds.
  //
  // A clamp here would be unreachable code -- it was in the first version
  // of this file, and the mutation that broke it scored exactly ZERO,
  // which is how it was found. The invariant is asserted in the bench
  // instead, where a violation would be visible.
  //
  // There is no phase-error guard here either, for the same reason: both
  // consumers below (`residue` and the two short-data tests) already
  // exclude the phase-error case themselves. Guarding it a second time
  // scored zero as well. Two dead guards, both found by mutations that
  // refused to die, and both deleted rather than explained away.
  logic [31:0] moved;
  assign moved = d_none ? 32'd0 : d_len;

  // dCSWDataResidue. On a phase error the field is defined but
  // meaningless to the host, which is about to reset the interface
  // anyway; it is reported as the full expectation so a trace reads
  // sensibly.
  logic [31:0] residue;
  assign residue = phase_err ? cbw_len : (cbw_len - moved);

  // Cases 4 and 9: the host allocated a data phase and the device has
  // nothing to put in it. The transport must terminate that phase
  // rather than leave the host waiting, and a STALL is how BOT says so.
  logic short_in;
  assign short_in = h_in  && !phase_err && (moved < cbw_len);
  logic short_out;
  assign short_out = h_out && !phase_err && (moved < cbw_len);

  // -------------------------------------------------------------------
  //  CBW validity. A CBW that is not 31 bytes, lacks the signature, or
  //  carries an out-of-range command length is not a CBW at all: the
  //  device stalls BOTH endpoints and waits for Reset Recovery. It must
  //  NOT answer with a CSW, because a CSW would imply it understood a
  //  command it did not.
  // -------------------------------------------------------------------
  logic cbw_ok;
  assign cbw_ok = cbw_sig_ok && cbw_cb_ok;

  logic [2:0]  st;
  logic [31:0] tag_r, res_r;
  logic [1:0]  sts_r;
  logic        csw_r, sin_r, sout_r, wedge_r;

  logic [31:0] cbw_c, pass_c, fail_c, phase_c, stall_c, inval_c;

  assign state       = st;
  assign csw_valid   = csw_r;
  assign csw_tag     = tag_r;
  assign csw_residue = res_r;
  assign csw_status  = sts_r;
  assign stall_in    = sin_r;
  assign stall_out   = sout_r;
  assign need_reset  = wedge_r;
  assign n_cbw       = cbw_c;
  assign n_pass      = pass_c;
  assign n_fail      = fail_c;
  assign n_phase     = phase_c;
  assign n_stall     = stall_c;
  assign n_invalid   = inval_c;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st      <= S_IDLE;
      tag_r   <= 32'd0;
      res_r   <= 32'd0;
      sts_r   <= ST_PASS;
      csw_r   <= 1'b0;
      sin_r   <= 1'b0;
      sout_r  <= 1'b0;
      wedge_r <= 1'b0;
      cbw_c   <= 32'd0;
      pass_c  <= 32'd0;
      fail_c  <= 32'd0;
      phase_c <= 32'd0;
      stall_c <= 32'd0;
      inval_c <= 32'd0;
    end else begin
      csw_r  <= 1'b0;
      sin_r  <= 1'b0;
      sout_r <= 1'b0;

      case (st)
        S_IDLE: begin
          if (cbw_valid) begin
            cbw_c <= cbw_c + 32'd1;
            if (!cbw_ok) begin
              // Not a CBW. Stall both ways and wedge until Reset
              // Recovery -- answering anything else would be inventing
              // a reply to a command that was never received.
              st      <= S_WEDGE;
              wedge_r <= 1'b1;
              sin_r   <= 1'b1;
              sout_r  <= 1'b1;
              inval_c <= inval_c + 32'd1;
            end else begin
              // THE TAG IS CAPTURED HERE, from the CBW, and echoed
              // unchanged. A device that regenerates it, or echoes the
              // previous one, breaks every host's command queue.
              tag_r <= cbw_tag;
              res_r <= residue;
              sts_r <= phase_err ? ST_PHASE : (dev_fail ? ST_FAIL : ST_PASS);

              if (phase_err) begin
                phase_c <= phase_c + 32'd1;
                // A phase error stalls the direction the host was
                // waiting on, so the host stops waiting and comes to
                // read the CSW.
                if (h_in)  sin_r  <= 1'b1;
                if (h_out) sout_r <= 1'b1;
                if (h_in || h_out) stall_c <= stall_c + 32'd1;
                st <= S_CSW;
              end else begin
                if (dev_fail) fail_c <= fail_c + 32'd1;
                else          pass_c <= pass_c + 32'd1;
                // Cases 4 and 9: terminate a data phase the device
                // cannot fill.
                if (short_in)  begin sin_r  <= 1'b1; stall_c <= stall_c + 32'd1; end
                if (short_out) begin sout_r <= 1'b1; stall_c <= stall_c + 32'd1; end
                st <= (h_none && d_none) ? S_CSW : S_DATA;
              end
            end
          end
        end

        // The data phase itself is not modelled byte by byte; what the
        // transport owes the host is the LENGTH decision above, and that
        // is already fixed.
        S_DATA: st <= S_CSW;

        S_CSW: begin
          csw_r <= 1'b1;
          st    <= S_IDLE;
        end

        // Wedged. Only a reset gets out, which is what Reset Recovery
        // means: the host has to clear both stalls and start again.
        S_WEDGE: begin
          sin_r  <= 1'b1;
          sout_r <= 1'b1;
        end

        default: st <= S_IDLE;
      endcase
    end
  end

endmodule

The SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for msc_bot_fsm -- SystemVerilog.
//
//  SAME SEED AND SAME PHASE ORDER AS THE VERILOG BENCH, deliberately.
//  Icarus seeds $random identically, so both drive identical stimulus and
//  any difference between the two mutation columns is a real difference
//  between the two DESIGNS. The independent-stimulus role is VHDL's.
//
//  THE MODEL IS THE SPEC'S TABLE, NOT THE DESIGN'S BOOLEANS.
//
//  The design decides with two expressions -- `dir_clash` and
//  `host_short` -- and derives everything from them. The model does the
//  opposite: it classifies the situation into one of the thirteen NAMED
//  cases the Bulk-Only Transport specification enumerates, and then
//  looks the answer up in a table written straight from that document.
//
//  Two different derivations of the same contract. A boolean the design
//  got subtly wrong cannot be got wrong the same way by a lookup keyed
//  on a case number, which is the entire reason for writing it this way
//  rather than more carefully.
//
//  Every valid-gated output is captured at a DEFINED instant. The stall
//  decision is only meaningful in the cycle the CBW is consumed; reading
//  it later reads a scheduling artefact.
// =====================================================================
`timescale 1ns/1ps
module tb_bt_sv;

  logic clk = 1'b0, rst_n = 1'b0;
  always #5 clk = ~clk;

  logic         cbw_valid = 1'b0;
  logic [31:0] cbw_tag = 32'd0, cbw_len = 32'd0;
  logic         cbw_dir_in = 1'b0, cbw_sig_ok = 1'b1, cbw_cb_ok = 1'b1;
  logic         dev_none = 1'b0, dev_dir_in = 1'b0, dev_fail = 1'b0;
  logic [31:0] dev_len = 32'd0;

  logic        csw_valid, stall_in, stall_out, need_reset;
  logic [31:0] csw_tag, csw_residue;
  logic [1:0]  csw_status;
  logic [2:0]  state;
  logic [31:0] n_cbw, n_pass, n_fail, n_phase, n_stall, n_invalid;

  msc_bot_fsm dut (
    .clk(clk), .rst_n(rst_n),
    .cbw_valid(cbw_valid), .cbw_tag(cbw_tag), .cbw_len(cbw_len),
    .cbw_dir_in(cbw_dir_in), .cbw_sig_ok(cbw_sig_ok), .cbw_cb_ok(cbw_cb_ok),
    .dev_none(dev_none), .dev_dir_in(dev_dir_in), .dev_len(dev_len),
    .dev_fail(dev_fail),
    .csw_valid(csw_valid), .csw_tag(csw_tag), .csw_residue(csw_residue),
    .csw_status(csw_status),
    .stall_in(stall_in), .stall_out(stall_out), .need_reset(need_reset),
    .state(state),
    .n_cbw(n_cbw), .n_pass(n_pass), .n_fail(n_fail), .n_phase(n_phase),
    .n_stall(n_stall), .n_invalid(n_invalid)
  );

  localparam [1:0] ST_PASS = 2'd0, ST_FAIL = 2'd1, ST_PHASE = 2'd2;

  int errors = 0, checks = 0, steps = 0;
  int seed;

  // ---- cumulative across resets ----
  //
  // Every reset_dut zeroes the DUT's own counters, so a summary that read
  // them directly would report only whatever happened after the last one.
  // Per-step checks still use the DUT counters; these are for the totals.
  int c_cbw = 0, c_pass = 0, c_fail = 0, c_phase = 0,
          c_stall = 0, c_invalid = 0;

  // $random is SIGNED: mask the sign bit before any modulo.
  function automatic logic [31:0] urand();
    return $random(seed) & 32'h3FFF_FFFF;
  endfunction

  task automatic ck(input logic cond, input string what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step#%0d: %s", $time, steps, what);
      end
    end
  endtask

  // ---- what the design said, sampled at a DEFINED instant ----
  logic        obs_sin, obs_sout, obs_wedge, obs_csw;
  logic [31:0] obs_tag, obs_res;
  logic [1:0]  obs_sts;

  // ---- how many times each of the thirteen cases was exercised ----
  int case_hits [1:13];

  // -------------------------------------------------------------------
  //  THE MODEL: classify into the spec's thirteen cases.
  //
  //  h: 0 = Hn (no data), 1 = Hi (device-to-host), 2 = Ho (host-to-device)
  //  d: 0 = Dn (no data), 1 = Di (device-to-host), 2 = Do (host-to-device)
  // -------------------------------------------------------------------
  function automatic logic [4:0] spec_case(input [1:0] h, input [1:0] d,
                           input [31:0] hl, input [31:0] dl);
    begin
      if      (h == 2'd0 && d == 2'd0)                spec_case = 5'd1;
      else if (h == 2'd0 && d == 2'd1)                spec_case = 5'd2;
      else if (h == 2'd0 && d == 2'd2)                spec_case = 5'd3;
      else if (h == 2'd1 && d == 2'd0)                spec_case = 5'd4;
      else if (h == 2'd1 && d == 2'd1 && hl >  dl)    spec_case = 5'd5;
      else if (h == 2'd1 && d == 2'd1 && hl == dl)    spec_case = 5'd6;
      else if (h == 2'd1 && d == 2'd1 && hl <  dl)    spec_case = 5'd7;
      else if (h == 2'd1 && d == 2'd2)                spec_case = 5'd8;
      else if (h == 2'd2 && d == 2'd0)                spec_case = 5'd9;
      else if (h == 2'd2 && d == 2'd2 && hl >  dl)    spec_case = 5'd10;
      else if (h == 2'd2 && d == 2'd2 && hl == dl)    spec_case = 5'd11;
      else if (h == 2'd2 && d == 2'd2 && hl <  dl)    spec_case = 5'd12;
      else                                            spec_case = 5'd13;
    end
  endfunction

  // Which cases are a phase error, straight from the specification.
  // Written as a set membership rather than as a condition, so it cannot
  // share an algebraic mistake with the design.
  function automatic logic is_phase(input [4:0] c);
    begin
      is_phase = (c == 5'd2)  || (c == 5'd3)  || (c == 5'd7) ||
                 (c == 5'd8)  || (c == 5'd12) || (c == 5'd13);
    end
  endfunction

  // -------------------------------------------------------------------
  //  Drive one CBW and check everything the transport owes the host.
  // -------------------------------------------------------------------
  task automatic run_cbw(input [31:0] tag, input [31:0] hlen, input hdir,
               input dnone, input ddir, input [31:0] dlen,
               input dfail, input sig_ok, input cb_ok);
    logic [1:0]  h, d;
    logic [4:0]  c;
    logic        e_phase, e_sin, e_sout;
    logic [31:0] e_moved, e_res;
    logic [1:0]  e_sts;
    logic [31:0] p0, f0, ph0, i0;
    int    g;
    begin
      // classify, from the inputs only
      h = (hlen == 32'd0) ? 2'd0 : (hdir ? 2'd1 : 2'd2);
      d = dnone           ? 2'd0 : (ddir ? 2'd1 : 2'd2);
      c = spec_case(h, d, hlen, dnone ? 32'd0 : dlen);
      e_phase = is_phase(c);

      // the expected answer, from the case number
      e_moved = e_phase ? 32'd0
              : (d == 2'd0) ? 32'd0
              : ((dlen > hlen) ? hlen : dlen);
      e_res   = e_phase ? hlen : (hlen - e_moved);
      e_sts   = e_phase ? ST_PHASE : (dfail ? ST_FAIL : ST_PASS);
      // A short or absent data phase must be terminated on the side the
      // host is waiting on; a phase error stalls that side too.
      e_sin   = e_phase ? (h == 2'd1) : ((h == 2'd1) && (e_moved < hlen));
      e_sout  = e_phase ? (h == 2'd2) : ((h == 2'd2) && (e_moved < hlen));

      p0 = n_pass; f0 = n_fail; ph0 = n_phase; i0 = n_invalid;

      cbw_valid  = 1'b1;  cbw_tag = tag; cbw_len = hlen; cbw_dir_in = hdir;
      cbw_sig_ok = sig_ok; cbw_cb_ok = cb_ok;
      dev_none   = dnone; dev_dir_in = ddir; dev_len = dlen; dev_fail = dfail;

      @(posedge clk); #1;
      // ---- captured at the instant the CBW is consumed ----
      obs_sin   = stall_in;
      obs_sout  = stall_out;
      obs_wedge = need_reset;
      cbw_valid = 1'b0;

      if (!(sig_ok && cb_ok)) begin
        // ---- PROPERTY 1: an invalid CBW is not answered ----
        //
        // The device must stall both endpoints and wait for Reset
        // Recovery. Replying with a CSW would tell the host the command
        // was understood, and the host would believe it.
        ck(obs_sin && obs_sout,
           "an invalid CBW did not stall both endpoints");
        ck(obs_wedge, "an invalid CBW did not request reset recovery");
        ck(n_invalid == i0 + 32'd1, "an invalid CBW was not counted");
        c_cbw = c_cbw + 1; c_invalid = c_invalid + 1;
        // and no CSW, ever -- checked over a bounded window
        obs_csw = 1'b0;
        for (g = 0; g < 8; g = g + 1) begin
          @(posedge clk); #1;
          if (csw_valid) obs_csw = 1'b1;
        end
        ck(!obs_csw, "an invalid CBW was answered with a CSW");
        ck(n_pass == p0 && n_fail == f0 && n_phase == ph0,
           "an invalid CBW moved a status counter");
        // reset out of the wedge, which is what Reset Recovery does
        rst_n = 1'b0; @(posedge clk); @(posedge clk); rst_n = 1'b1;
        @(posedge clk); #1;
        steps = steps + 1;
      end else begin
        case_hits[c] = case_hits[c] + 1;
        c_cbw = c_cbw + 1;
        if (e_sts == ST_PHASE) c_phase = c_phase + 1;
        else if (e_sts == ST_FAIL) c_fail = c_fail + 1;
        else c_pass = c_pass + 1;
        if (e_sin)  c_stall = c_stall + 1;
        if (e_sout) c_stall = c_stall + 1;

        // ---- PROPERTY 2a: the invariants the design RELIES on ----
        //
        // The design carries no clamp and no zeroing guard, because two
        // invariants make both unnecessary. Relying on an invariant is
        // fine; relying on one nobody checks is not, so they are checked
        // here -- and a violation would be a bench bug before it was a
        // design bug.
        if (!e_phase && d != 2'd0)
          ck(dlen <= hlen,
             "INVARIANT: a non-phase-error transfer wants more than the host allocated");
        if (dnone)
          ck(e_moved == 32'd0,
             "INVARIANT: a no-data command moved a non-zero number of bytes");

        // ---- PROPERTY 2: the stall decision matches the case ----
        ck(obs_sin  === e_sin,  "stall_in disagrees with the spec case");
        ck(obs_sout === e_sout, "stall_out disagrees with the spec case");
        ck(!obs_wedge, "a valid CBW requested reset recovery");

        // ---- wait, bounded, for the CSW ----
        obs_csw = 1'b0;
        for (g = 0; g < 8 && !obs_csw; g = g + 1) begin
          @(posedge clk); #1;
          if (csw_valid) begin
            obs_csw = 1'b1;
            obs_tag = csw_tag;
            obs_res = csw_residue;
            obs_sts = csw_status;
          end
        end
        // ---- PROPERTY 3: exactly one CSW per valid CBW ----
        //
        // Silence would hang the host forever: it is waiting on an IN
        // endpoint that will never produce anything.
        ck(obs_csw, "a valid CBW produced no CSW");

        if (obs_csw) begin
          // ---- PROPERTY 4: THE TAG IS ECHOED UNCHANGED ----
          //
          // dCSWTag must equal dCBWTag. A device that regenerates it, or
          // returns the previous one, breaks every host that has more
          // than one command in flight -- and the corruption is silent,
          // because both values are plausible 32-bit numbers.
          ck(obs_tag === tag, "dCSWTag does not echo dCBWTag");
          // ---- PROPERTY 5: the status matches the case ----
          ck(obs_sts === e_sts, "csw_status disagrees with the spec case");
          // ---- PROPERTY 6: the residue is expected minus actual ----
          ck(obs_res === e_res, "dCSWDataResidue disagrees with the spec case");
          // ---- PROPERTY 7: a residue never exceeds the expectation ----
          //
          // A residue larger than dCBWDataTransferLength is arithmetically
          // impossible and would be read by the host as an enormous
          // negative transfer.
          ck(obs_res <= hlen, "the residue exceeds what the host asked for");
        end

        // ---- PROPERTY 8: exactly one status counter moved ----
        if (e_sts == ST_PHASE)
          ck(n_phase == ph0 + 32'd1 && n_pass == p0 && n_fail == f0,
             "counters wrong for a phase error");
        else if (e_sts == ST_FAIL)
          ck(n_fail == f0 + 32'd1 && n_pass == p0 && n_phase == ph0,
             "counters wrong for a failed command");
        else
          ck(n_pass == p0 + 32'd1 && n_fail == f0 && n_phase == ph0,
             "counters wrong for a passing command");

        ck(state === 3'd0, "the transport did not return to IDLE after a CSW");
        steps = steps + 1;
      end
    end
  endtask

  task automatic reset_dut;
    begin
      rst_n = 1'b0; cbw_valid = 1'b0;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      @(posedge clk); #1;
    end
  endtask

  // ---- exhaustive reach ----
  //
  // hlen(3) x hdir(2) x dkind(3) x dlen(3) x dfail(2) = 108, every
  // combination an independent input. dlen and ddir are deliberately
  // swept even when the device declares no data, because IGNORING them
  // in that case is itself a property -- a device that let a stale
  // length leak into a no-data command would report a residue for bytes
  // that were never going to move.
  logic reach [0:107];
  int nr, ri;

  int hi_, hd, dk, dl_, df, k, idx;
  int HLEN [0:2];
  int DLEN [0:2];

  initial begin
    for (ri = 0; ri < 108; ri = ri + 1) reach[ri] = 1'b0;
    for (k = 1; k <= 13; k = k + 1) case_hits[k] = 0;
    HLEN[0] = 0; HLEN[1] = 8; HLEN[2] = 16;
    DLEN[0] = 0; DLEN[1] = 8; DLEN[2] = 16;
    seed = 32'd29001;

    reset_dut;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- the whole decision space.
    // =============================================================
    for (hi_ = 0; hi_ < 3; hi_ = hi_ + 1)
    for (hd  = 0; hd  < 2; hd  = hd + 1)
    for (dk  = 0; dk  < 3; dk  = dk + 1)
    for (dl_ = 0; dl_ < 3; dl_ = dl_ + 1)
    for (df  = 0; df  < 2; df  = df + 1) begin
      run_cbw(32'hA5A5_0000 + steps[31:0], HLEN[hi_][31:0], hd[0],
              (dk == 0), (dk == 1), DLEN[dl_][31:0], df[0], 1'b1, 1'b1);
      idx = ((((hi_ * 2 + hd) * 3 + dk) * 3 + dl_) * 2 + df);
      reach[idx] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED) -- every one of the thirteen cases, by name.
    //
    //  Phase 1 already reaches them all. This phase exists so the
    //  chapter can state that each NAMED case was exercised, and so a
    //  reader can find the one line that produces case 7.
    // =============================================================
    reset_dut;
    run_cbw(32'h0000_0001, 32'd0,  1'b0, 1'b1, 1'b0, 32'd0,  1'b0, 1'b1, 1'b1); // 1  Hn = Dn
    run_cbw(32'h0000_0002, 32'd0,  1'b0, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 2  Hn < Di
    run_cbw(32'h0000_0003, 32'd0,  1'b0, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 3  Hn < Do
    run_cbw(32'h0000_0004, 32'd16, 1'b1, 1'b1, 1'b0, 32'd0,  1'b0, 1'b1, 1'b1); // 4  Hi > Dn
    run_cbw(32'h0000_0005, 32'd16, 1'b1, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 5  Hi > Di
    run_cbw(32'h0000_0006, 32'd8,  1'b1, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 6  Hi = Di
    run_cbw(32'h0000_0007, 32'd8,  1'b1, 1'b0, 1'b1, 32'd16, 1'b0, 1'b1, 1'b1); // 7  Hi < Di
    run_cbw(32'h0000_0008, 32'd8,  1'b1, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 8  Hi <> Do
    run_cbw(32'h0000_0009, 32'd16, 1'b0, 1'b1, 1'b0, 32'd0,  1'b0, 1'b1, 1'b1); // 9  Ho > Dn
    run_cbw(32'h0000_000A, 32'd16, 1'b0, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 10 Ho > Do
    run_cbw(32'h0000_000B, 32'd8,  1'b0, 1'b0, 1'b0, 32'd8,  1'b0, 1'b1, 1'b1); // 11 Ho = Do
    run_cbw(32'h0000_000C, 32'd8,  1'b0, 1'b0, 1'b0, 32'd16, 1'b0, 1'b1, 1'b1); // 12 Ho < Do
    run_cbw(32'h0000_000D, 32'd8,  1'b0, 1'b0, 1'b1, 32'd8,  1'b0, 1'b1, 1'b1); // 13 Ho <> Di

    // =============================================================
    //  PHASE 3 (DIRECTED) -- the tag, over many distinct values.
    //
    //  The tag is the one field with no arithmetic in it, which makes it
    //  the easiest to get wrong in a way nothing else notices. Sixty-four
    //  distinct tags, including the ends of the range and values that a
    //  truncating implementation would alias together.
    // =============================================================
    reset_dut;
    for (k = 0; k < 64; k = k + 1) begin : tags
      logic [31:0] t;
      case (k % 4)
        0: t = 32'h0000_0000 + k;
        1: t = 32'hFFFF_FFFF - k;
        2: t = 32'h0000_FFFF + (k << 16);
        default: t = {k[7:0], ~k[7:0], k[7:0], ~k[7:0]};
      endcase
      run_cbw(t, 32'd8, 1'b1, 1'b0, 1'b1, 32'd8, 1'b0, 1'b1, 1'b1);
      ck(obs_tag === t, "a distinct tag was not echoed exactly");
    end

    // =============================================================
    //  PHASE 4 (DIRECTED, EXHAUSTIVE) -- CBW validity.
    //
    //  Signature and command length, all four combinations. Three of
    //  them are not a CBW at all, and the device must wedge rather than
    //  guess.
    // =============================================================
    //  Swept across every host length and direction as well, because the
    //  wedge must happen REGARDLESS of what the rest of the CBW claimed --
    //  the device has not understood the command and must not act on any
    //  part of it. 4 validity combinations x 3 lengths x 2 directions = 24,
    //  of which 18 are invalid.
    //
    //  The first version tested the four validity combinations once each,
    //  which gave the mutation that answers an invalid CBW a domain of
    //  three and a score of nine. Widening a property costs nothing and
    //  turns an uninformative number into one that means something.
    reset_dut;
    for (k = 0; k < 4; k = k + 1)
    for (hi_ = 0; hi_ < 3; hi_ = hi_ + 1)
    for (hd = 0; hd < 2; hd = hd + 1)
      run_cbw(32'hDEAD_0000 + k * 8 + hi_ * 2 + hd, HLEN[hi_][31:0], hd[0],
              1'b0, 1'b1, 32'd8, 1'b0, k[1], k[0]);

    // =============================================================
    //  PHASE 5 (RANDOM)
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 600; k = k + 1)
      run_cbw(urand(), (urand() % 3) * 8, (urand() % 2),
              (urand() % 3) == 0, (urand() % 2), (urand() % 3) * 8,
              (urand() % 4) == 0, 1'b1, 1'b1);
`endif

    nr = 0; for (ri = 0; ri < 108; ri = ri + 1) if (reach[ri]) nr = nr + 1;

    $display("steps=%0d checks=%0d reach=%0d/108 errors=%0d",
             steps, checks, nr, errors);
    $display("[bot] cbws=%0d pass=%0d fail=%0d phase_error=%0d stalls=%0d invalid=%0d",
             c_cbw, c_pass, c_fail, c_phase, c_stall, c_invalid);
    $display("--- the thirteen Bulk-Only Transport cases, times exercised ---");
    $display("    case:  1    2    3    4    5    6    7    8    9   10   11   12   13");
    $write("    hits:");
    for (k = 1; k <= 13; k = k + 1) $write("%5d", case_hits[k]);
    $display("");
    for (k = 1; k <= 13; k = k + 1)
      ck(case_hits[k] > 0, "a Bulk-Only Transport case was never exercised");
    if (nr != 108) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

Same seed and phase order as the Verilog bench, so any difference between those two mutation columns is a real difference between the designs. Their outputs are byte-identical, including the case-hit distribution.

8. VHDL-2008

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  msc_bot_fsm -- Bulk-Only Transport, VHDL-2008.
--
--  CLASSIFICATION: simplified synthesisable teaching RTL.
--  Same hardware contract as the Verilog and SystemVerilog files: same
--  ports, same widths, same reset values, same cycle-by-cycle behaviour.
--
--  This is NOT a mass-storage device. There is no SCSI decoder, no media
--  and no FIFO. It is the TRANSPORT: the wrapper around every command,
--  and the arithmetic that decides what the host is told.
--
--  A flash drive is three bulk transfers in a loop -- CBW out, data,
--  CSW in -- and the interesting part is the third. The host states an
--  expectation in the CBW BEFORE the device has looked at the command;
--  the device then discovers what it actually wants to do. Those two can
--  disagree in length and in direction, and the specification enumerates
--  exactly THIRTEEN cases of that disagreement.
--
--  Getting one cell of that table wrong is invisible against every
--  well-behaved host and unrecoverable when it fires, because the two
--  ends then disagree about how many bytes are on the wire and no amount
--  of retrying fixes it.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity msc_bot_fsm is
  port (
    clk   : in std_logic;
    rst_n : in std_logic;

    -- the Command Block Wrapper, 31 bytes OUT
    cbw_valid  : in std_logic;
    cbw_tag    : in std_logic_vector(31 downto 0);  -- MUST come back in the CSW
    cbw_len    : in std_logic_vector(31 downto 0);  -- the host's expectation
    cbw_dir_in : in std_logic;                      -- 1 = device-to-host
    cbw_sig_ok : in std_logic;                      -- dCBWSignature == 'USBC'
    cbw_cb_ok  : in std_logic;                      -- bCBWCBLength in 1..16

    -- what the command layer says it will actually do, available only
    -- AFTER the CBW has been parsed -- which is why disagreement is
    -- possible at all
    dev_none   : in std_logic;
    dev_dir_in : in std_logic;
    dev_len    : in std_logic_vector(31 downto 0);
    dev_fail   : in std_logic;

    -- the Command Status Wrapper, 13 bytes IN
    csw_valid   : out std_logic;
    csw_tag     : out std_logic_vector(31 downto 0);
    csw_residue : out std_logic_vector(31 downto 0);
    csw_status  : out std_logic_vector(1 downto 0);

    stall_in   : out std_logic;
    stall_out  : out std_logic;
    need_reset : out std_logic;

    state     : out std_logic_vector(2 downto 0);
    n_cbw     : out std_logic_vector(31 downto 0);
    n_pass    : out std_logic_vector(31 downto 0);
    n_fail    : out std_logic_vector(31 downto 0);
    n_phase   : out std_logic_vector(31 downto 0);
    n_stall   : out std_logic_vector(31 downto 0);
    n_invalid : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of msc_bot_fsm is

  constant ST_PASS  : std_logic_vector(1 downto 0) := "00";
  constant ST_FAIL  : std_logic_vector(1 downto 0) := "01";
  constant ST_PHASE : std_logic_vector(1 downto 0) := "10";

  type bot_state_t is (S_IDLE, S_DATA, S_CSW, S_WEDGE);

  -- Exported as a 3-bit code so all three languages present one identical
  -- observable contract to their benches.
  function st_code (s : bot_state_t) return std_logic_vector is
  begin
    case s is
      when S_IDLE  => return "000";
      when S_DATA  => return "001";
      when S_CSW   => return "010";
      when S_WEDGE => return "011";
    end case;
  end function;

  signal st : bot_state_t := S_IDLE;

  -- the host's stated expectation, as the spec's three kinds
  signal h_none, h_in, h_out : std_logic;
  -- the device's intent
  signal d_none, d_in, d_out : std_logic;
  signal d_len : unsigned(31 downto 0);

  signal dir_clash, host_short, phase_err : std_logic;
  signal moved, residue : unsigned(31 downto 0);
  signal short_in, short_out : std_logic;
  signal cbw_ok : std_logic;

  signal tag_r : std_logic_vector(31 downto 0) := (others => '0');
  signal res_r : unsigned(31 downto 0) := (others => '0');
  signal sts_r : std_logic_vector(1 downto 0) := ST_PASS;
  signal csw_r, sin_r, sout_r, wedge_r : std_logic := '0';

  signal cbw_c, pass_c, fail_c, phase_c, stall_c, inval_c
         : unsigned(31 downto 0) := (others => '0');

  -- How many stalls this CBW raises, computed combinationally so the
  -- counter takes one write rather than one per direction.
  signal n_stall_now : unsigned(31 downto 0);

begin

  h_none <= '1' when unsigned(cbw_len) = 0 else '0';
  h_in   <= '1' when (h_none = '0' and cbw_dir_in = '1') else '0';
  h_out  <= '1' when (h_none = '0' and cbw_dir_in = '0') else '0';

  d_none <= dev_none;
  d_in   <= '1' when (d_none = '0' and dev_dir_in = '1') else '0';
  d_out  <= '1' when (d_none = '0' and dev_dir_in = '0') else '0';
  -- dev_len is read only through d_in / d_out below, both false when the
  -- command moves no data, so no zeroing guard is needed here.
  d_len  <= unsigned(dev_len);

  -- -------------------------------------------------------------------
  --  THE THIRTEEN CASES. Direction disagreement is always fatal (8, 13);
  --  so is the host under-allocating (2, 3, 7, 12) -- there is no way to
  --  move more bytes than the host set aside and no way to say so except
  --  by declaring the phase lost. Everything else is survivable and the
  --  residue carries the shortfall.
  -- -------------------------------------------------------------------
  dir_clash <= '1' when ((h_in = '1' and d_out = '1') or
                         (h_out = '1' and d_in = '1')) else '0';        -- 8, 13
  host_short <= '1' when ((h_none = '1' and d_none = '0') or            -- 2, 3
                          (h_in = '1' and d_in = '1' and unsigned(cbw_len) < d_len) or   -- 7
                          (h_out = '1' and d_out = '1' and unsigned(cbw_len) < d_len))   -- 12
                else '0';
  phase_err <= dir_clash or host_short;

  -- No clamp to cbw_len, and no phase-error guard. Every case where the
  -- device wants more than the host allocated is ALREADY a phase error,
  -- and both consumers below exclude the phase-error case themselves.
  -- Both guards were present in the first version of this design and both
  -- were found to be unreachable by mutations that scored exactly zero.
  moved <= (others => '0') when d_none = '1' else d_len;

  residue <= unsigned(cbw_len) when phase_err = '1'
             else unsigned(cbw_len) - moved;

  -- Cases 4 and 9: the host allocated a data phase the device cannot
  -- fill. The transport must terminate it rather than leave the host
  -- waiting, and a STALL is how BOT says so.
  short_in  <= '1' when (h_in = '1' and phase_err = '0' and moved < unsigned(cbw_len))
               else '0';
  short_out <= '1' when (h_out = '1' and phase_err = '0' and moved < unsigned(cbw_len))
               else '0';

  cbw_ok <= cbw_sig_ok and cbw_cb_ok;

  stalls : process (all)
    variable c : unsigned(31 downto 0);
  begin
    c := (others => '0');
    if phase_err = '1' then
      if h_in = '1' or h_out = '1' then c := to_unsigned(1, 32); end if;
    else
      if short_in  = '1' then c := c + 1; end if;
      if short_out = '1' then c := c + 1; end if;
    end if;
    n_stall_now <= c;
  end process;

  state       <= st_code(st);
  csw_valid   <= csw_r;
  csw_tag     <= tag_r;
  csw_residue <= std_logic_vector(res_r);
  csw_status  <= sts_r;
  stall_in    <= sin_r;
  stall_out   <= sout_r;
  need_reset  <= wedge_r;
  n_cbw       <= std_logic_vector(cbw_c);
  n_pass      <= std_logic_vector(pass_c);
  n_fail      <= std_logic_vector(fail_c);
  n_phase     <= std_logic_vector(phase_c);
  n_stall     <= std_logic_vector(stall_c);
  n_invalid   <= std_logic_vector(inval_c);

  process (clk, rst_n)
  begin
    if rst_n = '0' then
      st      <= S_IDLE;
      tag_r   <= (others => '0');
      res_r   <= (others => '0');
      sts_r   <= ST_PASS;
      csw_r   <= '0';
      sin_r   <= '0';
      sout_r  <= '0';
      wedge_r <= '0';
      cbw_c   <= (others => '0');
      pass_c  <= (others => '0');
      fail_c  <= (others => '0');
      phase_c <= (others => '0');
      stall_c <= (others => '0');
      inval_c <= (others => '0');
    elsif rising_edge(clk) then
      csw_r  <= '0';
      sin_r  <= '0';
      sout_r <= '0';

      case st is

        when S_IDLE =>
          if cbw_valid = '1' then
            cbw_c <= cbw_c + 1;
            if cbw_ok = '0' then
              -- Not a CBW. Stall both ways and wedge until Reset Recovery;
              -- answering anything else would invent a reply to a command
              -- that was never received.
              st      <= S_WEDGE;
              wedge_r <= '1';
              sin_r   <= '1';
              sout_r  <= '1';
              inval_c <= inval_c + 1;
            else
              -- THE TAG IS CAPTURED HERE and echoed unchanged. A device
              -- that regenerates it, or echoes the previous one, breaks
              -- every host with more than one command in flight.
              tag_r <= cbw_tag;
              res_r <= residue;
              if phase_err = '1' then
                sts_r   <= ST_PHASE;
                phase_c <= phase_c + 1;
                if h_in = '1'  then sin_r  <= '1'; end if;
                if h_out = '1' then sout_r <= '1'; end if;
                stall_c <= stall_c + n_stall_now;
                st <= S_CSW;
              else
                if dev_fail = '1' then
                  sts_r  <= ST_FAIL;
                  fail_c <= fail_c + 1;
                else
                  sts_r  <= ST_PASS;
                  pass_c <= pass_c + 1;
                end if;
                if short_in  = '1' then sin_r  <= '1'; end if;
                if short_out = '1' then sout_r <= '1'; end if;
                stall_c <= stall_c + n_stall_now;
                if h_none = '1' and d_none = '1' then
                  st <= S_CSW;
                else
                  st <= S_DATA;
                end if;
              end if;
            end if;
          end if;

        -- The data phase is not modelled byte by byte; what the transport
        -- owes the host is the LENGTH decision above, already fixed.
        when S_DATA =>
          st <= S_CSW;

        when S_CSW =>
          csw_r <= '1';
          st    <= S_IDLE;

        -- Wedged. Only a reset gets out, which is what Reset Recovery
        -- means: the host clears both stalls and starts again.
        when S_WEDGE =>
          sin_r  <= '1';
          sout_r <= '1';

      end case;
    end if;
  end process;

end architecture;

The VHDL testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  Testbench for msc_bot_fsm -- VHDL-2008.
--
--  THE MODEL IS THE SPEC'S TABLE, NOT THE DESIGN'S BOOLEANS.
--
--  The design decides with two expressions -- dir_clash and host_short --
--  and derives everything from them. The model does the opposite: it
--  classifies into one of the thirteen NAMED cases the Bulk-Only
--  Transport specification enumerates, then looks the answer up in a
--  table written straight from that document. Two derivations of one
--  contract.
--
--  THIS IS THE INDEPENDENT BENCH. The directed phases are structurally
--  identical to the Verilog and SystemVerilog benches, so the DIRECTED
--  mutation columns must agree EXACTLY and any disagreement is a real
--  finding. The random phase uses a VHDL-native generator.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;

entity tb_bt_vhdl is
  generic (
    DIRECTED_ONLY : boolean := false
  );
end entity;

architecture sim of tb_bt_vhdl is

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

  signal cbw_valid  : std_logic := '0';
  signal cbw_tag    : std_logic_vector(31 downto 0) := (others => '0');
  signal cbw_len    : std_logic_vector(31 downto 0) := (others => '0');
  signal cbw_dir_in : std_logic := '0';
  signal cbw_sig_ok : std_logic := '1';
  signal cbw_cb_ok  : std_logic := '1';
  signal dev_none   : std_logic := '0';
  signal dev_dir_in : std_logic := '0';
  signal dev_len    : std_logic_vector(31 downto 0) := (others => '0');
  signal dev_fail   : std_logic := '0';

  signal csw_valid   : std_logic;
  signal csw_tag     : std_logic_vector(31 downto 0);
  signal csw_residue : std_logic_vector(31 downto 0);
  signal csw_status  : std_logic_vector(1 downto 0);
  signal stall_in    : std_logic;
  signal stall_out   : std_logic;
  signal need_reset  : std_logic;
  signal state       : std_logic_vector(2 downto 0);
  signal n_cbw, n_pass, n_fail, n_phase, n_stall, n_invalid
         : std_logic_vector(31 downto 0);

  constant ST_PASS  : std_logic_vector(1 downto 0) := "00";
  constant ST_FAIL  : std_logic_vector(1 downto 0) := "01";
  constant ST_PHASE : std_logic_vector(1 downto 0) := "10";

begin

  dut : entity work.msc_bot_fsm
    port map (
      clk => clk, rst_n => rst_n,
      cbw_valid => cbw_valid, cbw_tag => cbw_tag, cbw_len => cbw_len,
      cbw_dir_in => cbw_dir_in, cbw_sig_ok => cbw_sig_ok, cbw_cb_ok => cbw_cb_ok,
      dev_none => dev_none, dev_dir_in => dev_dir_in, dev_len => dev_len,
      dev_fail => dev_fail,
      csw_valid => csw_valid, csw_tag => csw_tag, csw_residue => csw_residue,
      csw_status => csw_status,
      stall_in => stall_in, stall_out => stall_out, need_reset => need_reset,
      state => state,
      n_cbw => n_cbw, n_pass => n_pass, n_fail => n_fail, n_phase => n_phase,
      n_stall => n_stall, n_invalid => n_invalid
    );

  clkgen : process
  begin
    while not done loop
      clk <= '0'; wait for 5 ns;
      clk <= '1'; wait for 5 ns;
    end loop;
    wait;
  end process;

  main : process

    variable errors : integer := 0;
    variable checks : integer := 0;
    variable steps  : integer := 0;
    variable lo     : line;

    -- cumulative across resets: every reset_dut zeroes the DUT's counters
    variable c_cbw, c_pass, c_fail, c_phase, c_stall, c_invalid : integer := 0;

    -- what the design said, sampled at a DEFINED instant
    variable obs_sin, obs_sout, obs_wedge, obs_csw : boolean := false;
    variable obs_tag : std_logic_vector(31 downto 0) := (others => '0');
    variable obs_res : unsigned(31 downto 0) := (others => '0');
    variable obs_sts : std_logic_vector(1 downto 0) := "00";

    type hits_t is array (1 to 13) of integer;
    variable case_hits : hits_t := (others => 0);

    type reach_t is array (0 to 107) of boolean;
    variable reach : reach_t := (others => false);
    variable nr : integer := 0;

    type len3_t is array (0 to 2) of integer;
    constant HLEN : len3_t := (0, 8, 16);
    constant DLEN : len3_t := (0, 8, 16);

    variable rnd_state : unsigned(31 downto 0) := x"000F0701";
    impure function urand return integer is
    begin
      rnd_state := resize(rnd_state * to_unsigned(1103515245, 32), 32)
                   + to_unsigned(12345, 32);
      -- The HIGH bits. In an LCG with a power-of-two modulus bit i has
      -- period 2**(i+1), so `urand mod 4` off the low bits cycles
      -- 3,0,1,2,... in lockstep while its histogram stays perfectly
      -- uniform -- a defect that once emptied a whole random phase.
      return to_integer(rnd_state(30 downto 15));
    end function;

    procedure ck (cond : boolean; what : string) is
    begin
      checks := checks + 1;
      if not cond then
        errors := errors + 1;
        if errors <= 20 then
          write(lo, string'("  ERROR @") & time'image(now) &
                    string'(" step#") & integer'image(steps) &
                    string'(": ") & what);
          writeline(output, lo);
        end if;
      end if;
    end procedure;

    -- -----------------------------------------------------------------
    --  THE MODEL: classify into the spec's thirteen cases.
    --  h: 0 = Hn, 1 = Hi, 2 = Ho      d: 0 = Dn, 1 = Di, 2 = Do
    -- -----------------------------------------------------------------
    function spec_case (h, d, hl, dl : integer) return integer is
    begin
      if    h = 0 and d = 0 then return 1;
      elsif h = 0 and d = 1 then return 2;
      elsif h = 0 and d = 2 then return 3;
      elsif h = 1 and d = 0 then return 4;
      elsif h = 1 and d = 1 and hl >  dl then return 5;
      elsif h = 1 and d = 1 and hl =  dl then return 6;
      elsif h = 1 and d = 1 and hl <  dl then return 7;
      elsif h = 1 and d = 2 then return 8;
      elsif h = 2 and d = 0 then return 9;
      elsif h = 2 and d = 2 and hl >  dl then return 10;
      elsif h = 2 and d = 2 and hl =  dl then return 11;
      elsif h = 2 and d = 2 and hl <  dl then return 12;
      else  return 13;
      end if;
    end function;

    -- Which cases are a phase error, straight from the specification.
    -- Written as set membership so it cannot share an algebraic mistake
    -- with the design.
    function is_phase (c : integer) return boolean is
    begin
      return c = 2 or c = 3 or c = 7 or c = 8 or c = 12 or c = 13;
    end function;

    procedure reset_dut is
    begin
      rst_n <= '0';
      cbw_valid <= '0';
      wait until rising_edge(clk);
      wait until rising_edge(clk);
      rst_n <= '1';
      wait until rising_edge(clk);
      wait for 1 ns;
    end procedure;

    -- -----------------------------------------------------------------
    --  Drive one CBW and check everything the transport owes the host.
    -- -----------------------------------------------------------------
    procedure run_cbw (tag : std_logic_vector(31 downto 0);
                       hlen : integer; hdir : boolean;
                       dnone : boolean; ddir : boolean; dlen : integer;
                       dfail : boolean; sig_ok : boolean; cb_ok : boolean) is
      variable h, d, c : integer;
      variable e_phase, e_sin, e_sout : boolean;
      variable e_moved, e_res : integer;
      variable e_sts : std_logic_vector(1 downto 0);
      variable p0, f0, ph0, i0 : integer;
    begin
      if hlen = 0 then h := 0; elsif hdir then h := 1; else h := 2; end if;
      if dnone then d := 0; elsif ddir then d := 1; else d := 2; end if;
      if dnone then c := spec_case(h, d, hlen, 0);
      else          c := spec_case(h, d, hlen, dlen); end if;
      e_phase := is_phase(c);

      if e_phase then e_moved := 0;
      elsif d = 0 then e_moved := 0;
      elsif dlen > hlen then e_moved := hlen;
      else e_moved := dlen; end if;

      if e_phase then e_res := hlen; else e_res := hlen - e_moved; end if;
      if e_phase then e_sts := ST_PHASE;
      elsif dfail then e_sts := ST_FAIL;
      else e_sts := ST_PASS; end if;

      if e_phase then e_sin := (h = 1); else e_sin := (h = 1) and (e_moved < hlen); end if;
      if e_phase then e_sout := (h = 2); else e_sout := (h = 2) and (e_moved < hlen); end if;

      p0  := to_integer(unsigned(n_pass));
      f0  := to_integer(unsigned(n_fail));
      ph0 := to_integer(unsigned(n_phase));
      i0  := to_integer(unsigned(n_invalid));

      cbw_valid <= '1';
      cbw_tag   <= tag;
      cbw_len   <= std_logic_vector(to_unsigned(hlen, 32));
      if hdir  then cbw_dir_in <= '1'; else cbw_dir_in <= '0'; end if;
      if sig_ok then cbw_sig_ok <= '1'; else cbw_sig_ok <= '0'; end if;
      if cb_ok  then cbw_cb_ok  <= '1'; else cbw_cb_ok  <= '0'; end if;
      if dnone then dev_none <= '1'; else dev_none <= '0'; end if;
      if ddir  then dev_dir_in <= '1'; else dev_dir_in <= '0'; end if;
      dev_len <= std_logic_vector(to_unsigned(dlen, 32));
      if dfail then dev_fail <= '1'; else dev_fail <= '0'; end if;

      wait until rising_edge(clk);
      wait for 1 ns;
      -- captured at the instant the CBW is consumed
      obs_sin   := (stall_in  = '1');
      obs_sout  := (stall_out = '1');
      obs_wedge := (need_reset = '1');
      cbw_valid <= '0';

      if not (sig_ok and cb_ok) then
        -- ---- PROPERTY 1: an invalid CBW is not answered ----
        ck(obs_sin and obs_sout, "an invalid CBW did not stall both endpoints");
        ck(obs_wedge, "an invalid CBW did not request reset recovery");
        ck(to_integer(unsigned(n_invalid)) = i0 + 1, "an invalid CBW was not counted");
        c_cbw := c_cbw + 1; c_invalid := c_invalid + 1;
        obs_csw := false;
        for g in 0 to 7 loop
          wait until rising_edge(clk);
          wait for 1 ns;
          if csw_valid = '1' then obs_csw := true; end if;
        end loop;
        ck(not obs_csw, "an invalid CBW was answered with a CSW");
        ck(to_integer(unsigned(n_pass)) = p0 and
           to_integer(unsigned(n_fail)) = f0 and
           to_integer(unsigned(n_phase)) = ph0,
           "an invalid CBW moved a status counter");
        rst_n <= '0';
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        rst_n <= '1';
        wait until rising_edge(clk);
        wait for 1 ns;
        steps := steps + 1;
      else
        case_hits(c) := case_hits(c) + 1;
        c_cbw := c_cbw + 1;
        if    e_sts = ST_PHASE then c_phase := c_phase + 1;
        elsif e_sts = ST_FAIL  then c_fail  := c_fail + 1;
        else                        c_pass  := c_pass + 1; end if;
        if e_sin  then c_stall := c_stall + 1; end if;
        if e_sout then c_stall := c_stall + 1; end if;

        -- ---- PROPERTY 2a: the invariants the design RELIES on ----
        --
        -- The design carries no clamp and no phase-error guard on `moved`,
        -- because two invariants make both unnecessary. Relying on an
        -- invariant is fine; relying on one nobody checks is not.
        if (not e_phase) and d /= 0 then
          ck(dlen <= hlen,
             "INVARIANT: a non-phase-error transfer wants more than the host allocated");
        end if;
        if dnone then
          ck(e_moved = 0,
             "INVARIANT: a no-data command moved a non-zero number of bytes");
        end if;

        -- ---- PROPERTY 2: the stall decision matches the case ----
        ck(obs_sin  = e_sin,  "stall_in disagrees with the spec case");
        ck(obs_sout = e_sout, "stall_out disagrees with the spec case");
        ck(not obs_wedge, "a valid CBW requested reset recovery");

        obs_csw := false;
        for g in 0 to 7 loop
          if not obs_csw then
            wait until rising_edge(clk);
            wait for 1 ns;
            if csw_valid = '1' then
              obs_csw := true;
              obs_tag := csw_tag;
              obs_res := unsigned(csw_residue);
              obs_sts := csw_status;
            end if;
          end if;
        end loop;
        -- ---- PROPERTY 3: exactly one CSW per valid CBW ----
        ck(obs_csw, "a valid CBW produced no CSW");

        if obs_csw then
          -- ---- PROPERTY 4: THE TAG IS ECHOED UNCHANGED ----
          ck(obs_tag = tag, "dCSWTag does not echo dCBWTag");
          -- ---- PROPERTY 5: the status matches the case ----
          ck(obs_sts = e_sts, "csw_status disagrees with the spec case");
          -- ---- PROPERTY 6: the residue is expected minus actual ----
          --
          -- Compared AS UNSIGNED, never via to_integer. A mutant that
          -- breaks the phase-error detection lets the residue subtraction
          -- underflow to nearly 2**32, and converting that to VHDL's
          -- 32-bit signed INTEGER is a FATAL RUNTIME ERROR -- the mutant
          -- aborts the simulation instead of failing it, and the matrix
          -- reports a crash where it should report a score.
          --
          -- A mutant must fail, not abort. Comparing in the DUT's own type
          -- is what guarantees that.
          ck(obs_res = to_unsigned(e_res, 32),
             "dCSWDataResidue disagrees with the spec case");
          -- ---- PROPERTY 7: a residue never exceeds the expectation ----
          ck(obs_res <= to_unsigned(hlen, 32),
             "the residue exceeds what the host asked for");
        end if;

        -- ---- PROPERTY 8: exactly one status counter moved ----
        if e_sts = ST_PHASE then
          ck(to_integer(unsigned(n_phase)) = ph0 + 1 and
             to_integer(unsigned(n_pass)) = p0 and
             to_integer(unsigned(n_fail)) = f0, "counters wrong for a phase error");
        elsif e_sts = ST_FAIL then
          ck(to_integer(unsigned(n_fail)) = f0 + 1 and
             to_integer(unsigned(n_pass)) = p0 and
             to_integer(unsigned(n_phase)) = ph0, "counters wrong for a failed command");
        else
          ck(to_integer(unsigned(n_pass)) = p0 + 1 and
             to_integer(unsigned(n_fail)) = f0 and
             to_integer(unsigned(n_phase)) = ph0, "counters wrong for a passing command");
        end if;

        ck(state = "000", "the transport did not return to IDLE after a CSW");
        steps := steps + 1;
      end if;
    end procedure;

    variable idx : integer;
    variable tv  : std_logic_vector(31 downto 0);

  begin
    reset_dut;

    -- ===============================================================
    --  PHASE 1 (DIRECTED, EXHAUSTIVE) -- the whole decision space.
    --  hlen(3) x hdir(2) x dkind(3) x dlen(3) x dfail(2) = 108.
    -- ===============================================================
    for hix in 0 to 2 loop
      for hd in 0 to 1 loop
        for dk in 0 to 2 loop
          for dlx in 0 to 2 loop
            for df in 0 to 1 loop
              run_cbw(std_logic_vector(unsigned'(x"A5A50000") + to_unsigned(steps, 32)),
                      HLEN(hix), hd = 1, dk = 0, dk = 1, DLEN(dlx), df = 1,
                      true, true);
              idx := ((((hix * 2 + hd) * 3 + dk) * 3 + dlx) * 2 + df);
              reach(idx) := true;
            end loop;
          end loop;
        end loop;
      end loop;
    end loop;

    -- ===============================================================
    --  PHASE 2 (DIRECTED) -- every one of the thirteen cases, by name.
    -- ===============================================================
    reset_dut;
    run_cbw(x"00000001",  0, false, true,  false,  0, false, true, true); -- 1  Hn = Dn
    run_cbw(x"00000002",  0, false, false, true,   8, false, true, true); -- 2  Hn < Di
    run_cbw(x"00000003",  0, false, false, false,  8, false, true, true); -- 3  Hn < Do
    run_cbw(x"00000004", 16, true,  true,  false,  0, false, true, true); -- 4  Hi > Dn
    run_cbw(x"00000005", 16, true,  false, true,   8, false, true, true); -- 5  Hi > Di
    run_cbw(x"00000006",  8, true,  false, true,   8, false, true, true); -- 6  Hi = Di
    run_cbw(x"00000007",  8, true,  false, true,  16, false, true, true); -- 7  Hi < Di
    run_cbw(x"00000008",  8, true,  false, false,  8, false, true, true); -- 8  Hi <> Do
    run_cbw(x"00000009", 16, false, true,  false,  0, false, true, true); -- 9  Ho > Dn
    run_cbw(x"0000000A", 16, false, false, false,  8, false, true, true); -- 10 Ho > Do
    run_cbw(x"0000000B",  8, false, false, false,  8, false, true, true); -- 11 Ho = Do
    run_cbw(x"0000000C",  8, false, false, false, 16, false, true, true); -- 12 Ho < Do
    run_cbw(x"0000000D",  8, false, false, true,   8, false, true, true); -- 13 Ho <> Di

    -- ===============================================================
    --  PHASE 3 (DIRECTED) -- the tag, over many distinct values.
    --
    --  The tag is the one field with no arithmetic in it, which makes it
    --  the easiest to get wrong in a way nothing else notices.
    -- ===============================================================
    reset_dut;
    for k in 0 to 63 loop
      case k mod 4 is
        when 0 => tv := std_logic_vector(to_unsigned(k, 32));
        when 1 => tv := std_logic_vector(unsigned'(x"FFFFFFFF") - to_unsigned(k, 32));
        when 2 => tv := std_logic_vector(unsigned'(x"0000FFFF") +
                                         shift_left(to_unsigned(k, 32), 16));
        when others =>
          tv := std_logic_vector(to_unsigned(k mod 256, 8)) &
                std_logic_vector(not to_unsigned(k mod 256, 8)) &
                std_logic_vector(to_unsigned(k mod 256, 8)) &
                std_logic_vector(not to_unsigned(k mod 256, 8));
      end case;
      run_cbw(tv, 8, true, false, true, 8, false, true, true);
      ck(obs_tag = tv, "a distinct tag was not echoed exactly");
    end loop;

    -- ===============================================================
    --  PHASE 4 (DIRECTED, EXHAUSTIVE) -- CBW validity.
    --
    --  Swept across every host length and direction as well, because the
    --  wedge must happen REGARDLESS of what the rest of the CBW claimed.
    --  4 validity combinations x 3 lengths x 2 directions = 24, of which
    --  18 are invalid.
    -- ===============================================================
    reset_dut;
    for k in 0 to 3 loop
      for hix in 0 to 2 loop
        for hd in 0 to 1 loop
          run_cbw(std_logic_vector(unsigned'(x"DEAD0000") +
                                   to_unsigned(k * 8 + hix * 2 + hd, 32)),
                  HLEN(hix), hd = 1, false, true, 8, false,
                  (k / 2) = 1, (k mod 2) = 1);
        end loop;
      end loop;
    end loop;

    -- ===============================================================
    --  PHASE 5 (RANDOM)
    -- ===============================================================
    if not DIRECTED_ONLY then
      reset_dut;
      for k in 0 to 599 loop
        run_cbw(std_logic_vector(to_unsigned(urand mod 65536, 32)),
                (urand mod 3) * 8, (urand mod 2) = 1,
                (urand mod 3) = 0, (urand mod 2) = 1, (urand mod 3) * 8,
                (urand mod 4) = 0, true, true);
      end loop;
    end if;

    nr := 0;
    for i in 0 to 107 loop
      if reach(i) then nr := nr + 1; end if;
    end loop;

    write(lo, string'("steps=") & integer'image(steps) &
              string'(" checks=") & integer'image(checks) &
              string'(" reach=") & integer'image(nr) &
              string'("/108 errors=") & integer'image(errors));
    writeline(output, lo);
    write(lo, string'("[bot] cbws=") & integer'image(c_cbw) &
              string'(" pass=") & integer'image(c_pass) &
              string'(" fail=") & integer'image(c_fail) &
              string'(" phase_error=") & integer'image(c_phase) &
              string'(" stalls=") & integer'image(c_stall) &
              string'(" invalid=") & integer'image(c_invalid));
    writeline(output, lo);
    write(lo, string'("--- the thirteen Bulk-Only Transport cases, times exercised ---"));
    writeline(output, lo);
    write(lo, string'("    case:  1    2    3    4    5    6    7    8    9   10   11   12   13"));
    writeline(output, lo);
    write(lo, string'("    hits:"));
    for k in 1 to 13 loop
      write(lo, case_hits(k), right, 5);
    end loop;
    writeline(output, lo);
    for k in 1 to 13 loop
      ck(case_hits(k) > 0, "a Bulk-Only Transport case was never exercised");
    end loop;

    if nr /= 108 then
      write(lo, string'("FAIL: exhaustive sweep incomplete")); writeline(output, lo);
      errors := errors + 1;
    end if;
    if errors = 0 then
      write(lo, string'("PASS: 0 errors in ") & integer'image(checks) & string'(" checks"));
    else
      write(lo, string'("FAIL: ") & integer'image(errors) &
                string'(" errors in ") & integer'image(checks) & string'(" checks"));
    end if;
    writeline(output, lo);

    done <= true;
    wait;
  end process;

end architecture;

9. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
//  Properties for msc_bot_fsm.
//
//  NOT SIMULATED IN THIS CHAPTER. Icarus Verilog does not support
//  concurrent assertions, so every number published here comes from the
//  procedural checks in the testbenches. These are the same obligations
//  in the form a commercial simulator or a formal tool would take.
// ---------------------------------------------------------------------
module msc_bot_sva (
  input logic        clk,
  input logic        rst_n,
  input logic        cbw_valid,
  input logic [31:0] cbw_tag,
  input logic [31:0] cbw_len,
  input logic        cbw_sig_ok,
  input logic        cbw_cb_ok,
  input logic        csw_valid,
  input logic [31:0] csw_tag,
  input logic [31:0] csw_residue,
  input logic [1:0]  csw_status,
  input logic        stall_in,
  input logic        stall_out,
  input logic        need_reset,
  input logic [2:0]  state
);

  localparam logic [1:0] ST_PASS = 2'd0, ST_FAIL = 2'd1, ST_PHASE = 2'd2;
  localparam logic [2:0] S_IDLE = 3'd0, S_DATA = 3'd1,
                         S_CSW = 3'd2, S_WEDGE = 3'd3;

  default clocking cb @(posedge clk); endclocking
  default disable iff (!rst_n);

  // ---- 1. the CSW is a single-cycle pulse ----
  // A status held for two cycles is counted twice by any consumer that
  // simply samples the flag, and the host would see two completions for
  // one command.
  a_csw_pulse : assert property (csw_valid |=> !csw_valid);

  // ---- 2. THE TAG IS ECHOED ----
  //
  // dCSWTag must equal the dCBWTag of the command being answered. This is
  // the property with no arithmetic in it, which makes it the easiest to
  // break in a way nothing else notices: every value is a plausible 32-bit
  // number.
  property p_tag_echo;
    (cbw_valid && cbw_sig_ok && cbw_cb_ok)
      |-> ##[1:4] (csw_valid && (csw_tag == $past(cbw_tag, 1)));
  endproperty
  // (The $past depth is written for the concrete pipeline; a formal tool
  //  would carry the tag in an auxiliary variable instead.)

  // ---- 3. the residue never exceeds the expectation ----
  //
  // A residue larger than dCBWDataTransferLength is arithmetically
  // impossible, and a host reading one interprets it as an enormous
  // transfer. This is the property that catches an underflowed
  // subtraction, which is exactly what a missed phase error produces.
  a_residue_bound : assert property
    (csw_valid |-> (csw_residue <= $past(cbw_len, 2)));

  // ---- 4. the status is one of the three defined codes ----
  // 2'b11 is reserved; emitting it would be a value the host has no
  // interpretation for.
  a_status_legal : assert property
    (csw_valid |-> (csw_status inside {ST_PASS, ST_FAIL, ST_PHASE}));

  // ---- 5. an invalid CBW is NEVER answered ----
  //
  // THE property of this design. A CSW after an unparseable CBW tells the
  // host the device understood a command it never received, and the host
  // believes it. The device must wedge instead and wait for Reset
  // Recovery.
  property p_invalid_wedges;
    (cbw_valid && !(cbw_sig_ok && cbw_cb_ok))
      |=> (need_reset && stall_in && stall_out && !csw_valid);
  endproperty
  a_invalid_wedges : assert property (p_invalid_wedges);

  // ---- 6. the wedge is sticky until reset ----
  // Reset Recovery means exactly that: nothing short of a reset clears it.
  a_wedge_sticky : assert property
    ((state == S_WEDGE) |=> (state == S_WEDGE));

  // ---- 7. a CSW only ever follows a CBW ----
  // The transport never speaks unprompted.
  a_csw_prompted : assert property
    (csw_valid |-> $past(state) == S_CSW);

  // ---- 8. both endpoints are stalled only when wedged ----
  //
  // A single command stalls at most ONE direction -- the one the host is
  // waiting on. Stalling both is reserved for "this was not a CBW", and
  // conflating the two would make Reset Recovery fire on ordinary short
  // reads.
  a_double_stall : assert property
    ((stall_in && stall_out) |-> need_reset);

  // ---- COVER: the fatal cases are actually reached ----
  // A suite of passing assertions over stimulus that never produces a
  // phase error has tested the happy path and called it the protocol.
  c_phase   : cover property (csw_valid && (csw_status == ST_PHASE));
  c_fail    : cover property (csw_valid && (csw_status == ST_FAIL));
  c_residue : cover property (csw_valid && (csw_residue != 0));
  c_wedge   : cover property (need_reset);
  c_stall_i : cover property (stall_in && !need_reset);
  c_stall_o : cover property (stall_out && !need_reset);

endmodule

10. Where UVM Fits

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
//  UVM structure for Bulk-Only Transport.
//
//  NOT SIMULATED IN THIS CHAPTER. Icarus cannot compile UVM -- it breaks
//  on virtual method dispatch -- so every number comes from the procedural
//  benches. This is the structure a production environment would use.
//
//  THE DESIGN DECISION: the sequence item carries the HOST's claim and the
//  DEVICE's intent as two independent fields, and the constraint block is
//  written so they can DISAGREE. An environment whose stimulus derives one
//  from the other can never reach cases 2, 3, 7, 8, 12 or 13 -- which is
//  to say it can never reach any of the fatal ones.
// ---------------------------------------------------------------------

class bot_command extends uvm_sequence_item;
  `uvm_object_utils(bot_command)

  // what the host puts in the CBW
  rand bit [31:0] tag;
  rand int        host_len;
  rand bit        host_dir_in;

  // what the device turns out to want -- INDEPENDENT of the above
  rand bit        dev_none;
  rand bit        dev_dir_in;
  rand int        dev_len;
  rand bit        dev_fail;

  // CBW validity, so the wedge path is reachable
  rand bit        sig_ok;
  rand bit        cb_ok;

  constraint c_sane {
    host_len inside {0, 8, 16, 512};
    dev_len  inside {0, 8, 16, 512};
  }

  // Malformed CBWs are rare but must not be impossible: the wedge is the
  // one path with no recovery except a reset, and a regression that never
  // reaches it has not tested the worst outcome the transport has.
  constraint c_validity { sig_ok dist {1 := 95, 0 := 5};
                          cb_ok  dist {1 := 95, 0 := 5}; }

  // THE IMPORTANT ONE. Nothing ties dev_* to host_*, and that is
  // deliberate. A tempting constraint like
  //     dev_len <= host_len;
  // would make the environment well-behaved and delete six of the thirteen
  // cases -- including both families of fatal disagreement.
  constraint c_reach_the_fatal_cases {
    // bias TOWARDS disagreement, because a real host rarely produces it
    // and the rare cases are the ones that ship broken
    dev_dir_in dist { host_dir_in := 70, (!host_dir_in) := 30 };
  }

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

// ---- the scoreboard IS the thirteen-case table ----
//
// Identical classification to the procedural benches, and for the same
// reason: it must not share a derivation with the design.
class bot_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(bot_scoreboard)

  int unsigned case_hits[14];
  int unsigned n_tag_mismatch, n_bad_residue;

  function int classify(bot_command c);
    int h = (c.host_len == 0) ? 0 : (c.host_dir_in ? 1 : 2);
    int d = c.dev_none        ? 0 : (c.dev_dir_in  ? 1 : 2);
    int dl = c.dev_none ? 0 : c.dev_len;
    if (h == 0 && d == 0) return 1;
    if (h == 0 && d == 1) return 2;
    if (h == 0 && d == 2) return 3;
    if (h == 1 && d == 0) return 4;
    if (h == 1 && d == 1) return (c.host_len > dl) ? 5 : (c.host_len == dl) ? 6 : 7;
    if (h == 1 && d == 2) return 8;
    if (h == 2 && d == 0) return 9;
    if (h == 2 && d == 2) return (c.host_len > dl) ? 10 : (c.host_len == dl) ? 11 : 12;
    return 13;
  endfunction

  function void report_phase(uvm_phase phase);
    // Every case must have been reached. A regression that missed one has
    // not tested the transport, however many commands it ran.
    for (int i = 1; i <= 13; i++)
      if (case_hits[i] == 0)
        `uvm_error("BOT", $sformatf("spec case %0d was never exercised", i))
    if (n_tag_mismatch)
      `uvm_error("BOT", $sformatf("%0d CSWs did not echo their CBW tag", n_tag_mismatch))
  endfunction
endclass

// ---- coverage: the CASES, not the commands ----
class bot_coverage extends uvm_subscriber #(bot_command);
  `uvm_component_utils(bot_coverage)

  int spec_case;

  covergroup cg with function sample(bot_command c, int sc);
    // THE coverpoint. Line coverage on this design reaches 100% long
    // before case 7 has ever happened, which is precisely the gap.
    cp_case : coverpoint sc { bins b[] = {[1:13]}; }
    cp_fail : coverpoint c.dev_fail;
    cp_sig  : coverpoint c.sig_ok;
    // A failing command crossed with each case, because "the command
    // failed" and "the phase was impossible" are different statuses and a
    // device can confuse them.
    x_fail  : cross cp_case, cp_fail;
  endgroup

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

  function void write(bot_command t);
    cg.sample(t, spec_case);
  endfunction
endclass

11. Mutation Testing

Nine defects, injected one at a time into all three languages. Every replacement asserted; each mutation generated as its own file.

#the injected defectV-allV-dirSV-allSV-dirVHDL-allVHDL-dir
BASEunmodified design000000
P1dCSWTag is regenerated, not echoed855255855255855255
P2residue is bytes moved, not bytes missing324109324109318109
P3direction disagreement not detected (8, 13)554955549549195
P4host under-allocation not detected (2, 3, 7, 12)677126677126710126
P5a short data phase is never terminated250412504123441
P6an invalid CBW is answered with a CSW545454545454
P7the direction bit is read with inverted polarity180554018055401776540
P8off-by-one: an exact-length transfer stalls110751107511675
P9a phase error is reported as an ordinary failure349623496235962

Every mutation is killed, and every one by directed stimulus alone. The directed column is identical across all three languages at all nine rows.

Two mutations that refused to die, and what they were telling me

P7 and P8 are not the defects they started as. The originals scored exactly zero, twice in a row, and each time the answer was the same: the mutation was breaking code that could never execute.

attemptwhat it brokescorewhat that meant
1a clamp of moved to cbw_len0unreachable: d_len > cbw_len is already a phase error
2a phase-error guard on moved0unreachable: both consumers of moved exclude phase errors themselves
3the direction-bit polarity540a real defect

P6's 54 came from widening a property, not from finding a bug

P6 — answering an invalid CBW — first scored 9. That was exactly its domain: the CBW-validity phase tested the four signature/length combinations once each, three of which are invalid, and each invalid one runs three checks.

Sweeping the same property across every host length and direction as well — because the wedge must happen regardless of what the rest of the CBW claimed — took it to 54. No bug was found. An uninformative number became one that means something.

Run totals

stepschecksreacherrors
Verilog, full8098,519108 / 1080
Verilog, directed only2092,206108 / 1080
SystemVerilog, full8098,519108 / 1080
SystemVerilog, directed only2092,206108 / 1080
VHDL, full8098,509108 / 1080
VHDL, directed only2092,206108 / 1080

The directed-only rows are identical across all three languages in every column, including the case-hit distribution. The full rows differ only in VHDL's check count, by 10, from its independent random stream.

12. What This Does Not Cover

No SCSI. The command layer is an input to this design, not part of it. READ(10), WRITE(10), INQUIRY, REQUEST SENSE and the rest are a separate and much larger subject; what the transport needs from them is three values — direction, length, pass/fail — and those are ports.

No media, no FIFO, no wear levelling. A real drive spends most of its silicon on flash management. None of it changes the transport.

The data phase is not modelled byte by byte. What the transport owes the host is the length decision, and that is fixed before the first byte moves. Modelling the bytes would add volume without adding a case.

No Get Max LUN or Bulk-Only Mass Storage Reset. Both are class-specific control requests, and both matter — the reset is half of Reset Recovery. They belong to the control endpoint, which is module 13's subject.

Reset Recovery is modelled as a reset. The real sequence is a class reset followed by clearing both endpoint halts, and a host that skips a step leaves the device wedged. The design's obligation — stay wedged until a reset — is what is checked here.

Lengths are 0, 8 and 16 bytes. The field is 32 bits. Those three values reach every structural case; the remaining four billion add points, not cases.

13. The Interview Answer

"How does a USB flash drive work?" is usually answered with NAND and wear levelling, which is the storage answer. The USB answer is better:

1. Name the loop. "Bulk-Only Transport: a 31-byte command wrapper out, an optional data phase, a 13-byte status wrapper in. Two bulk endpoints, and that is the whole protocol."

2. Name the hard part. "The host declares how many bytes it expects and in which direction before the device has parsed the command. Those two can disagree, and the spec enumerates thirteen cases of disagreement — six of them fatal, in two families: wrong direction, and the host allocating less than the device needs."

3. Name the field that does the work. "dCSWDataResidue — expected minus actual. It is how a short read reports success rather than failure, and a device that returns an error for a short read makes every end-of-data look like a fault."

If there is time, the detail worth offering is the tag: dCSWTag must echo dCBWTag, and a device that regenerates it works perfectly against a host with one command in flight and corrupts completions against a host with two. It is the field with no arithmetic in it, which is what makes it easy to break silently.

14. What Carries Forward

This chapter's mechanism was a decision table with fatal cells — small, completely enumerable, and dangerous precisely because the dangerous cells are rare.

The next chapter changes that shape entirely. A webcam does not have a decision table; it has a firehose. Isochronous transfers have no retries at all, so there is no error to handle and no status to return — a packet that goes missing is simply gone, and the device's only job is to make sure the next frame is not damaged by it too.

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.