Skip to content
VLSI Mentor

USB · Module 31

“Enumeration Is Optional”

Nobody says this out loud; they act on it — by testing a data path before the device has an address, and by debugging an endpoint the host never opened a pipe to. A state machine with one gated output is the whole refutation.

1. The Belief

"Enumeration is host-side housekeeping. The device gets an address and the OS builds its device tree. The real work — my endpoint, my data — is separate from it, and I can test that part first."

Almost nobody states this as a proposition. Almost everybody acts on it, which is worse: an unstated belief cannot be argued with.

2. Why An Intelligent Engineer Believes It

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    IT IS INVISIBLE
    Enumeration happens in milliseconds, before any user-visible
    behaviour, and if it works you never see it. The engineer's entire
    experience of USB begins AFTER it succeeded.

    IT IS SOMEBODY ELSE'S CODE
    On the host it is the stack's job; on the device it is often a
    vendor's library, a soft-IP block, or ROM firmware. It arrives
    working. Nothing about it looks like the thing being designed.

    IT LOOKS LIKE ADMINISTRATION
    "Assign an address, read descriptors, pick a configuration" reads
    like a registration desk -- bookkeeping performed on a device that
    already has all its capabilities.

    EVERY OTHER BUS THE ENGINEER KNOWS WORKS THAT WAY
      I2C        address the slave and transact. No handshake first.
      SPI        assert chip select and clock. Nothing negotiated.
      AXI        the port exists because it is wired
      UART       there is not even an address

    On all four, a peripheral is USABLE THE MOMENT IT IS CONNECTED.
    That is the mental model being imported, and on those buses it is
    completely correct.

3. The Prediction It Makes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    IF ENUMERATION WERE OPTIONAL HOUSEKEEPING, THEN:

    1  a device's data endpoints would work as soon as it is attached,
       or at least as soon as it has an address

    2  a device with correct data-path silicon and wrong descriptors
       would still move data, just be described badly

    3  a bus reset would not undo anything important -- an address,
       once given, would stay given

    4  enumeration could be skipped or stubbed for bring-up, and the
       endpoint tested directly

    5  a device that "does nothing" would have a fault in its function

Prediction 5 is the expensive one, and it is the one an engineer acts on without ever having formed it deliberately.

4. The Counterexample

The counterexample is not exotic. It is the most common bring-up experience in the industry.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    A DEVICE WITH A PERFECT DATA PATH AND ONE WRONG DESCRIPTOR BYTE

    o  the silicon is correct. Every endpoint buffer works, verified in
       simulation, verified on the bench against a directed stimulus
    o  the descriptor declares a maximum packet size, or a total
       length, or a configuration count, incorrectly
    o  the host reads the descriptors, finds them inconsistent, and
       does not complete configuration

    WHAT THE ENGINEER SEES
      the device is "not recognised", or it appears and disappears, or
      it enumerates and no pipe ever opens
      NO endpoint ever receives a token
      the data path -- which is flawless -- is never exercised at all

    WHAT THE ENGINEER DOES, under the belief
      debugs the data path

And the second counterexample, which falsifies prediction 3 and is a genuine hardware trap:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    A BUS RESET RETURNS THE DEVICE TO DEFAULT

    o  address: gone. The device answers at address 0 again.
    o  configuration: gone. Every non-control endpoint STOPS EXISTING.
    o  a device that was working is now a thing on a wire.

    And a bus reset is not rare. It happens on resume, on error
    recovery, when a driver reloads, when the port is re-initialised.
    A device that treats its address as permanent works until the first
    one.

What must happen before a function endpoint can carry one byte

A sequence diagram of enumeration. The device attaches and the host drives a bus reset, putting the device in the Default state at address zero. The host reads the device descriptor over endpoint zero, sets an address, reads the configuration descriptors, and selects a configuration. Only after the configuration is selected does the host issue a token to a function endpoint and receive data. A dashed message earlier in the sequence shows a token to a function endpoint before configuration being rejected rather than served.Attached, Default, Address, ConfiguredHostDevice, endpoint 0Device, function endpointattached --electrically presentbus reset -> stateDEFAULT, address 0a token here is anERROR, not atransferGET_DESCRIPTOR(device)descriptor bytesSET_ADDRESS -> stateADDRESSGET_DESCRIPTOR(configuration)endpoints, sizes,bandwidth requestedSET_CONFIGURATION ->state CONFIGUREDIN token -- NOW thisendpoint existsDATA
Read downward. The device is electrically attached from the first line, and the function endpoint is unusable for every message above the last two. Endpoint 0 is the only channel in use for the whole negotiation — it is how the device is asked what it is. The dashed grey message third from the top is a token to a function endpoint before configuration: it is not a transfer, it is an error, and the belief predicts it would work.

Two features of that diagram are the chapter. The first message shows the device attached, and there are eight messages between it and the first byte of function data. And the third message is the belief: a token to a function endpoint before configuration, drawn in red because it cannot be a transfer.

5. The Corrected Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    FIVE THINGS THAT ARE NOT THE SAME THING

      attached     there is a device on the wire. Electrically present.
      DEFAULT      it has been reset and answers at address 0
      ADDRESS      it has an address of its own
      CONFIGURED   a configuration is active, so its declared
                   endpoints EXIST
      usable       the function can move data

    Each line requires the line above it. The belief collapses all five
    into the first one.

    THE ONE-LINE INVARIANT
      a function endpoint is enabled  ->  the device is CONFIGURED

    AND THE EXCEPTION THAT MAKES THE SCHEME WORK
      endpoint 0 is enabled from ATTACHMENT, because it is the channel
      over which the other four lines are arranged. If it were gated
      like everything else, nothing could ever be negotiated.

    WHAT A BUS RESET DOES
      returns to DEFAULT: address gone, configuration gone, every
      non-control endpoint stops existing. It is a PROTOCOL-STATE
      event, not a chip reset -- 29.3 separated those, and this is
      where the separation earns its keep.

6. The Hardware Contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    PURPOSE     hold the device's protocol state, and gate the
                function's endpoints on it. That gate is the chapter.

    INPUTS      clk, rst_n
                attached             electrically present
                bus_reset            one cycle: the host drove a reset
                set_addr_valid, set_addr [6:0]
                set_cfg_valid,  set_cfg  [7:0]
                                     already-decoded standard requests
                n_configs [7:0]      how many configurations the
                                     descriptors declare

    OUTPUTS     dev_state [1:0]      DEFAULT / ADDRESS / CONFIGURED
                dev_addr  [6:0]
                cfg_value [7:0]
                ep0_en               endpoint 0 enabled
                fn_ep_en             function endpoints enabled
                five counters

    AUTHORITATIVE STATE
                state, addr_r, cfg_r

    DERIVED     THE TWO LINES THAT ARE THE WHOLE CHAPTER:
                  ep0_en   = attached;
                  fn_ep_en = attached && (state == ST_CONFIGURED);

    RESET       rst_n is a CHIP reset: state DEFAULT, address 0, no
                configuration. bus_reset is a PROTOCOL event with the
                same effect on protocol state and no effect on
                anything else.

    PRIORITY    detachment outranks every request. bus_reset outranks
                a request arriving in the same cycle: a reset and a
                SET_ADDRESS together leave the device in DEFAULT.

    LATENCY     one cycle per accepted request; the gate is
                combinational in the state.

    BOUNDARY    SET_CONFIGURATION with value 0 is legal and UNCONFIGURES
                -- it is not a rejection, and fn_ep_en falls.
                A configuration value above n_configs is REJECTED and
                changes nothing.
                SET_ADDRESS while CONFIGURED is rejected: an address
                change is not a mid-configuration operation.
                SET_ADDRESS with value 0 returns to DEFAULT -- "answer
                as nobody again".
                SET_CONFIGURATION from DEFAULT is REJECTED: the address
                step is not optional either, and E-M2 is exactly the
                removal of that condition.

    ASSUMPTIONS requests arrive pre-decoded as pulses. A real device
                reaches this point only after a complete control
                transfer with a data stage and a status stage.

    OMISSIONS   the control-transfer machine, SETUP packet decoding,
                the descriptor store, the data and status stages,
                string descriptors, alternate settings (31.3's
                mechanism), suspend and resume, and every electrical
                detail of attachment.

    MISCONCEPTION DEMONSTRATED
                "enumeration is optional -- attached is enough" and
                "the address step is a formality"

usb_fn_enable.v — the design, Verilog-2005

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_fn_enable -- the difference between being plugged in and being
//  usable, as a state machine.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL. It is NOT an
//  enumeration engine: there is no control-transfer machine, no
//  descriptor store, no SETUP decoding, no data stage. The requests
//  arrive here already decoded, as two pulses with a value each, and
//  what the module does with them is the whole subject.
//
//  THE FIVE THINGS THAT ARE NOT THE SAME THING
//  -------------------------------------------
//      attached     there is a device on the wire and the host can see
//                   it. Nothing else.
//      DEFAULT      it has been reset and answers at address zero.
//      ADDRESS      it has been given an address of its own.
//      CONFIGURED   a configuration has been selected, so the
//                   endpoints the descriptors describe now exist.
//      usable       the function can move data -- which requires the
//                   line above it, and nothing less.
//
//  THE INVARIANT
//      fn_ep_en |-> (dev_state == ST_CONFIGURED)
//
//  Endpoint zero is different and it is the reason the whole scheme
//  works: it is enabled as soon as the device is attached, because it
//  is the channel over which everything else is arranged. Every other
//  endpoint is gated. Mutation E-M1 replaces the gate with `attached`,
//  which is the misconception written as RTL.
// =====================================================================
module usb_fn_enable (
  input  wire       clk,
  input  wire       rst_n,

  // The device is electrically present and the host has seen it.
  input  wire       attached,
  // One cycle: the host drove a bus reset.
  input  wire       bus_reset,

  // Already-decoded standard requests.
  input  wire       set_addr_valid,
  input  wire [6:0] set_addr,
  input  wire       set_cfg_valid,
  input  wire [7:0] set_cfg,

  // How many configurations the descriptors declare. A request for one
  // that does not exist is not a state change.
  input  wire [7:0] n_configs,

  output wire [1:0] dev_state,
  output wire [6:0] dev_addr,
  output wire [7:0] cfg_value,

  // Endpoint zero: available from attachment, because it is how the
  // rest of this is negotiated.
  output wire       ep0_en,
  // Every other endpoint: available only when configured.
  output wire       fn_ep_en,

  output wire [15:0] n_resets,
  output wire [15:0] n_addressed,
  output wire [15:0] n_configured,
  output wire [15:0] n_rejected,
  // Cycles in which a non-control endpoint was enabled while the device
  // was not configured. Structurally impossible here; E-M1 makes it the
  // normal case.
  output wire [15:0] n_premature
);

  localparam [1:0] ST_DEFAULT    = 2'd0,
                   ST_ADDRESS    = 2'd1,
                   ST_CONFIGURED = 2'd2;

  reg [1:0]  state;
  reg [6:0]  addr_r;
  reg [7:0]  cfg_r;
  reg [15:0] c_rst, c_addr, c_cfg, c_rej, c_prem;

  // SET_ADDRESS is defined in Default and Address. SET_CONFIGURATION is
  // defined in Address and Configured. A request outside its states is
  // a request error, not a state change -- 30.3 built the table.
  wire addr_legal = set_addr_valid && attached &&
                    ((state == ST_DEFAULT) || (state == ST_ADDRESS));
  wire cfg_legal  = set_cfg_valid  && attached &&
                    ((state == ST_ADDRESS) || (state == ST_CONFIGURED)) &&
                    (set_cfg <= n_configs);

  wire req_rejected = (set_addr_valid && !addr_legal) ||
                      (set_cfg_valid  && !cfg_legal);

  assign ep0_en   = attached;
  assign fn_ep_en = attached && (state == ST_CONFIGURED);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state  <= ST_DEFAULT;
      addr_r <= 7'd0;
      cfg_r  <= 8'd0;
      c_rst  <= 16'd0; c_addr <= 16'd0; c_cfg <= 16'd0;
      c_rej  <= 16'd0; c_prem <= 16'd0;
    end else begin
      if (!attached) begin
        // Unplugging is not a protocol event the device negotiates. It
        // returns to the state it had before anybody knew it existed.
        state  <= ST_DEFAULT;
        addr_r <= 7'd0;
        cfg_r  <= 8'd0;
      end else if (bus_reset) begin
        // A bus reset returns the device to Default: address zero,
        // unconfigured. Everything the host arranged is undone, and
        // the host knows it because the host caused it.
        state  <= ST_DEFAULT;
        addr_r <= 7'd0;
        cfg_r  <= 8'd0;
        c_rst  <= c_rst + 16'd1;
      end else begin
        if (addr_legal) begin
          addr_r <= set_addr;
          // Address zero means "go back to answering as nobody".
          state  <= (set_addr == 7'd0) ? ST_DEFAULT : ST_ADDRESS;
          if (set_addr != 7'd0) c_addr <= c_addr + 16'd1;
        end
        if (cfg_legal) begin
          cfg_r <= set_cfg;
          // Configuration zero means "unconfigure" -- a legal request
          // that takes a working device back to having no endpoints.
          state <= (set_cfg == 8'd0) ? ST_ADDRESS : ST_CONFIGURED;
          if (set_cfg != 8'd0) c_cfg <= c_cfg + 16'd1;
        end
        if (req_rejected) c_rej <= c_rej + 16'd1;
      end

      if (fn_ep_en && (state != ST_CONFIGURED)) c_prem <= c_prem + 16'd1;
    end
  end

  assign dev_state    = state;
  assign dev_addr     = addr_r;
  assign cfg_value    = cfg_r;
  assign n_resets     = c_rst;
  assign n_addressed  = c_addr;
  assign n_configured = c_cfg;
  assign n_rejected   = c_rej;
  assign n_premature  = c_prem;

endmodule

7. The Testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    PHASE 1  THE GATE, stated in its own terms and consulting nothing.

      Plug the device in and leave it there. TWO HUNDRED CYCLES of being
      electrically present and visible to the host, and in every one of
      them require:

        fn_ep_en  == 0      the function endpoints stay dark
        ep0_en    == 1      and endpoint zero works the whole time

      then, after the 200:

        dev_state == DEFAULT        it did not drift
        dev_addr  == 0
        n_premature == 0            the impossible counter is still zero

      and only then do the work, one step at a time:

        SET_ADDRESS   -> addressed, and STILL NOT USABLE
        SET_CONFIG    -> configured: NOW usable

      If enumeration were optional, the 200-cycle interval is where the
      device would start working. It does not, and the loop counter is
      itself checked -- attached_run must reach 200, or a loop that
      never ran would have passed by not existing.

    PHASE 2  the sweep, over the named axes below

    PHASE 3  SCENARIOS -- the full enumeration; enumeration interrupted
             at each stage; a reset from each state; unconfigure and
             reconfigure; detach mid-enumeration; a reset arriving in
             the same cycle as a request

Phase 1's first check is the counterexample from section 4 as a test: 200 cycles attached and unusable. That is the shape of an intent check for a negative architectural claim — it cannot be a comparison, because a model that shared the belief would enable the endpoint too and the comparison would pass.

tb_usb_fn_enable.v — the testbench, Verilog-2005

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  tb_usb_fn_enable -- Verilog-2005 testbench for usb_fn_enable.
//
//  PHASE 1 is the intent phase and it consults no model. It states the
//  claim the chapter exists for and requires it every cycle:
//
//      a non-control endpoint is NEVER enabled unless the device is
//      configured -- no matter how long it has been plugged in
//
//  PHASES
//    1 INTENT      the gate, asserted directly, across a long attach
//    2 EXHAUSTIVE  every (state, request, value class) triple
//    3 SCENARIO    the full enumeration walk, and the ways back down
//    4 RANDOM      supplementary, audited
// =====================================================================
`timescale 1ns/1ps

module tb_usb_fn_enable;

  localparam [1:0] ST_DEFAULT = 2'd0, ST_ADDRESS = 2'd1, ST_CONFIGURED = 2'd2;

  reg       clk = 1'b0;
  reg       rst_n, attached, bus_reset;
  reg       set_addr_valid, set_cfg_valid;
  reg [6:0] set_addr;
  reg [7:0] set_cfg, n_configs;

  wire [1:0] dev_state;
  wire [6:0] dev_addr;
  wire [7:0] cfg_value;
  wire       ep0_en, fn_ep_en;
  wire [15:0] n_resets, n_addressed, n_configured, n_rejected, n_premature;

  usb_fn_enable dut (
    .clk(clk), .rst_n(rst_n), .attached(attached), .bus_reset(bus_reset),
    .set_addr_valid(set_addr_valid), .set_addr(set_addr),
    .set_cfg_valid(set_cfg_valid), .set_cfg(set_cfg),
    .n_configs(n_configs),
    .dev_state(dev_state), .dev_addr(dev_addr), .cfg_value(cfg_value),
    .ep0_en(ep0_en), .fn_ep_en(fn_ep_en),
    .n_resets(n_resets), .n_addressed(n_addressed),
    .n_configured(n_configured), .n_rejected(n_rejected),
    .n_premature(n_premature)
  );

  always #5 clk = ~clk;

  // ---- the independent reference model ---------------------------
  // The RULES, in words, then code:
  //   R1 unplugged is Default, address zero, unconfigured
  //   R2 a bus reset is the same, and is counted
  //   R3 SET_ADDRESS is defined in Default and Address only
  //   R4 SET_CONFIGURATION is defined in Address and Configured only,
  //      and only for a configuration that exists
  //   R5 value zero means "go back one step" in both cases
  reg [1:0]  rm_state;
  reg [6:0]  rm_addr;
  reg [7:0]  rm_cfg;
  reg [15:0] rm_rst, rm_adr, rm_cfgc, rm_rej;

  integer chk_dir, chk_rnd, err, in_random;
  integer m_attach_cycles, m_cfg_cycles, m_fn_cycles, m_resets,
          m_addressed, m_configured, m_rejected, m_unconfig, m_setupfail;
  integer i, j, st, rq, vc;

  task bump; begin
    if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
  end endtask

  task ck;
    input [255:0] what;
    input [31:0]  got;
    input [31:0]  exp;
    begin
      bump;
      if (got !== exp) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %0s: got %0d expected %0d  (t=%0t)", what, got, exp, $time);
      end
    end
  endtask

  task ref_step;
    reg a_ok, c_ok;
    begin
      if (!rst_n) begin
        rm_state = ST_DEFAULT; rm_addr = 0; rm_cfg = 0;
        rm_rst = 0; rm_adr = 0; rm_cfgc = 0; rm_rej = 0;
      end else if (!attached) begin
        rm_state = ST_DEFAULT; rm_addr = 0; rm_cfg = 0;   // R1
      end else if (bus_reset) begin
        rm_state = ST_DEFAULT; rm_addr = 0; rm_cfg = 0;   // R2
        rm_rst = rm_rst + 1;
        m_resets = m_resets + 1;
      end else begin
        a_ok = set_addr_valid &&
               ((rm_state == ST_DEFAULT) || (rm_state == ST_ADDRESS));   // R3
        c_ok = set_cfg_valid &&
               ((rm_state == ST_ADDRESS) || (rm_state == ST_CONFIGURED)) &&
               (set_cfg <= n_configs);                                   // R4
        if (a_ok) begin
          rm_addr  = set_addr;
          rm_state = (set_addr == 0) ? ST_DEFAULT : ST_ADDRESS;          // R5
          if (set_addr != 0) begin
            rm_adr = rm_adr + 1; m_addressed = m_addressed + 1;
          end
        end
        if (c_ok) begin
          rm_cfg   = set_cfg;
          rm_state = (set_cfg == 0) ? ST_ADDRESS : ST_CONFIGURED;        // R5
          if (set_cfg != 0) begin
            rm_cfgc = rm_cfgc + 1; m_configured = m_configured + 1;
          end else m_unconfig = m_unconfig + 1;
        end
        if ((set_addr_valid && !a_ok) || (set_cfg_valid && !c_ok)) begin
          rm_rej = rm_rej + 1; m_rejected = m_rejected + 1;
        end
      end
      if (attached) m_attach_cycles = m_attach_cycles + 1;
      if (rm_state == ST_CONFIGURED) m_cfg_cycles = m_cfg_cycles + 1;
    end
  endtask

  task cmp; begin
    ck("dev_state",    {30'd0, dev_state},   {30'd0, rm_state});
    ck("dev_addr",     {25'd0, dev_addr},    {25'd0, rm_addr});
    ck("cfg_value",    {24'd0, cfg_value},   {24'd0, rm_cfg});
    ck("ep0_en",       {31'd0, ep0_en},      {31'd0, attached});
    ck("fn_ep_en",     {31'd0, fn_ep_en},
                       {31'd0, (attached && (rm_state == ST_CONFIGURED))});
    ck("n_resets",     {16'd0, n_resets},    {16'd0, rm_rst});
    ck("n_addressed",  {16'd0, n_addressed}, {16'd0, rm_adr});
    ck("n_configured", {16'd0, n_configured},{16'd0, rm_cfgc});
    ck("n_rejected",   {16'd0, n_rejected},  {16'd0, rm_rej});
    ck("n_premature",  {16'd0, n_premature}, 32'd0);
    if (fn_ep_en) m_fn_cycles = m_fn_cycles + 1;
  end endtask

  // The claim, asserted directly, every cycle of every phase.
  task intent_check; begin
    bump;
    if (fn_ep_en && (dev_state != ST_CONFIGURED)) begin
      err = err + 1;
      $display("  ** INTENT VIOLATED: function endpoints enabled in state %0d (t=%0t)",
               dev_state, $time);
    end
    bump;
    if (fn_ep_en && !attached) begin
      err = err + 1;
      $display("  ** INTENT VIOLATED: function endpoints enabled while detached (t=%0t)",
               $time);
    end
  end endtask

  task step; begin
    #1;
    @(posedge clk);
    ref_step;
    #1;
    cmp;
    intent_check;
    bus_reset = 0; set_addr_valid = 0; set_cfg_valid = 0;
    set_addr = 0; set_cfg = 0;
  end endtask

  task idle; begin step; end endtask

  task hard_reset; begin
    rst_n = 0; attached = 0; bus_reset = 0;
    set_addr_valid = 0; set_addr = 0; set_cfg_valid = 0; set_cfg = 0;
    n_configs = 8'd2;
    repeat (3) begin @(posedge clk); ref_step; end
    #1; rst_n = 1;
    @(posedge clk); ref_step; #1; cmp;
  end endtask

  task plug;      begin attached = 1; step; end endtask
  task unplug;    begin attached = 0; step; end endtask
  task do_reset;  begin bus_reset = 1; step; end endtask
  task addr_req;  input [6:0] a;
    begin set_addr_valid = 1; set_addr = a; step; end
  endtask
  task cfg_req;   input [7:0] c;
    begin set_cfg_valid = 1; set_cfg = c; step; end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 1 -- THE GATE.
  //
  //  Plug the device in and leave it there. Two hundred cycles of being
  //  electrically present, visible to the host, with endpoint zero
  //  working the whole time -- and the function endpoints stay dark.
  //  If enumeration were optional, this is the interval in which the
  //  device would start working. It does not.
  // -----------------------------------------------------------------
  integer attached_run;

  task phase_intent;
    begin
      hard_reset;
      plug;
      ck("attached", {31'd0, attached}, 32'd1);
      ck("endpoint zero is up", {31'd0, ep0_en}, 32'd1);
      attached_run = 0;
      for (i = 0; i < 200; i = i + 1) begin
        idle;
        ck("still nothing usable", {31'd0, fn_ep_en}, 32'd0);
        ck("but EP0 still is",     {31'd0, ep0_en},   32'd1);
        attached_run = attached_run + 1;
      end
      ck("still in Default",      {30'd0, dev_state}, {30'd0, ST_DEFAULT});
      ck("still address zero",    {25'd0, dev_addr},  32'd0);
      ck("nothing premature",     {16'd0, n_premature}, 32'd0);
      // now do the work, and only then does the function exist
      addr_req(7'd5);
      ck("addressed, still not usable", {31'd0, fn_ep_en}, 32'd0);
      cfg_req(8'd1);
      ck("configured: now usable",      {31'd0, fn_ep_en}, 32'd1);
      bump;
      if (attached_run < 200) begin
        err = err + 1;
        $display("  ** intent: only %0d attached cycles", attached_run);
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 2 -- the exhaustive sweep.
  //
  //  AXES:
  //    device state       DEFAULT, ADDRESS, CONFIGURED              3
  //    request            none, SET_ADDRESS, SET_CONFIGURATION,
  //                       bus reset                                 4
  //    value class        zero, valid non-zero, out of range        3
  //    ---------------------------------------------------------------
  //                                        3 x 4 x 3 =             36
  //
  //  WHAT IS ABSENT: detachment, which is not a request and cannot be
  //  a value class. It is phase 3. An exhaustive sweep is exhaustive
  //  over the axes it has.
  // -----------------------------------------------------------------
  task setup_state;
    input [1:0] want;
    begin
      hard_reset;
      plug;
      if (want >= ST_ADDRESS)    addr_req(7'd9);
      if (want == ST_CONFIGURED) cfg_req(8'd1);
      bump;
      if (dev_state !== want) begin
        err = err + 1; m_setupfail = m_setupfail + 1;
        $display("  ** setup: wanted state %0d, reached %0d", want, dev_state);
      end
    end
  endtask

  task phase_sweep;
    begin
      for (st = 0; st < 3; st = st + 1)
      for (rq = 0; rq < 4; rq = rq + 1)
      for (vc = 0; vc < 3; vc = vc + 1) begin
        setup_state(st[1:0]);
        case (rq)
          0: idle;
          1: addr_req((vc == 0) ? 7'd0 : (vc == 1) ? 7'd17 : 7'd126);
          2: cfg_req ((vc == 0) ? 8'd0 : (vc == 1) ? 8'd1  : 8'd9);
          3: do_reset;
        endcase
        idle;
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 3 -- the walk, and every way back down.
  // -----------------------------------------------------------------
  task phase_scenarios;
    begin
      // S1 the whole sequence, one step at a time, checking that the
      //    function is unusable at every step but the last.
      hard_reset;
      ck("S1 detached: EP0 down",  {31'd0, ep0_en},   32'd0);
      plug;
      ck("S1 attached: EP0 up",    {31'd0, ep0_en},   32'd1);
      ck("S1 attached: fn down",   {31'd0, fn_ep_en}, 32'd0);
      do_reset;
      ck("S1 reset: Default",      {30'd0, dev_state},{30'd0, ST_DEFAULT});
      ck("S1 reset: fn down",      {31'd0, fn_ep_en}, 32'd0);
      addr_req(7'd12);
      ck("S1 addressed",           {30'd0, dev_state},{30'd0, ST_ADDRESS});
      ck("S1 addressed: fn down",  {31'd0, fn_ep_en}, 32'd0);
      ck("S1 the address stuck",   {25'd0, dev_addr}, 32'd12);
      cfg_req(8'd1);
      ck("S1 configured",          {30'd0, dev_state},{30'd0, ST_CONFIGURED});
      ck("S1 NOW it is usable",    {31'd0, fn_ep_en}, 32'd1);

      // S2 a bus reset undoes all of it, from Configured, in one cycle.
      do_reset;
      ck("S2 back to Default",     {30'd0, dev_state},{30'd0, ST_DEFAULT});
      ck("S2 address gone",        {25'd0, dev_addr}, 32'd0);
      ck("S2 function gone",       {31'd0, fn_ep_en}, 32'd0);

      // S3 SET_CONFIGURATION(0) is a legal request that unconfigures.
      hard_reset; plug; addr_req(7'd3); cfg_req(8'd1);
      ck("S3 usable",              {31'd0, fn_ep_en}, 32'd1);
      cfg_req(8'd0);
      ck("S3 unconfigured",        {30'd0, dev_state},{30'd0, ST_ADDRESS});
      ck("S3 not usable",          {31'd0, fn_ep_en}, 32'd0);
      ck("S3 but still addressed", {25'd0, dev_addr}, 32'd3);

      // S4 SET_ADDRESS(0) returns to Default from Address.
      hard_reset; plug; addr_req(7'd3);
      addr_req(7'd0);
      ck("S4 back to Default",     {30'd0, dev_state},{30'd0, ST_DEFAULT});

      // S5 SET_CONFIGURATION before an address is a request error, and
      //    the device does NOT become usable by asking nicely.
      hard_reset; plug;
      cfg_req(8'd1);
      ck("S5 rejected",            {16'd0, n_rejected}, 32'd1);
      ck("S5 still Default",       {30'd0, dev_state},  {30'd0, ST_DEFAULT});
      ck("S5 still not usable",    {31'd0, fn_ep_en},   32'd0);

      // S6 a configuration that does not exist is refused.
      hard_reset; plug; addr_req(7'd4);
      cfg_req(8'd7);
      ck("S6 refused",             {16'd0, n_rejected}, 32'd1);
      ck("S6 still only addressed",{30'd0, dev_state},  {30'd0, ST_ADDRESS});

      // S7 unplugging a configured device takes everything with it.
      hard_reset; plug; addr_req(7'd6); cfg_req(8'd2);
      ck("S7 usable",              {31'd0, fn_ep_en},   32'd1);
      unplug;
      ck("S7 EP0 down",            {31'd0, ep0_en},     32'd0);
      ck("S7 function down",       {31'd0, fn_ep_en},   32'd0);
      ck("S7 Default again",       {30'd0, dev_state},  {30'd0, ST_DEFAULT});
      plug;
      ck("S7 replug is not resume",{31'd0, fn_ep_en},   32'd0);
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 4 -- random, audited.
  // -----------------------------------------------------------------
  task phase_random;
    integer r;
    begin
      in_random = 1;
      hard_reset; plug;
      for (j = 0; j < 4000; j = j + 1) begin
        r = {$random} % 100;
        if (r < 22)      addr_req({$random} % 128);
        else if (r < 44) cfg_req({$random} % 4);
        else if (r < 54) do_reset;
        else if (r < 60) begin unplug; plug; end
        else if (r < 64) begin
          n_configs = {$random} % 4;
          idle;
        end else idle;
      end
      in_random = 0;
    end
  endtask

  initial begin
    chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
    m_attach_cycles=0; m_cfg_cycles=0; m_fn_cycles=0; m_resets=0;
    m_addressed=0; m_configured=0; m_rejected=0; m_unconfig=0;
    m_setupfail=0;

    phase_intent;
    $display("  phase 1 intent      : %0d checks, %0d errors  (%0d attached cycles, unusable)",
             chk_dir, err, attached_run);
    phase_sweep;
    $display("  phase 2 exhaustive  : %0d checks, %0d errors  (36 combinations)", chk_dir, err);
    phase_scenarios;
    $display("  phase 3 scenarios   : %0d checks, %0d errors", chk_dir, err);
    $display("  ---- DIRECTED-ONLY  : %0d checks, %0d errors ----", chk_dir, err);
    phase_random;

    $display("");
    $display("  measured reachability (all phases)");
    $display("    cycles attached ........ %0d", m_attach_cycles);
    $display("    cycles configured ...... %0d", m_cfg_cycles);
    $display("    cycles function enabled  %0d", m_fn_cycles);
    $display("    bus resets ............. %0d", m_resets);
    $display("    addresses assigned ..... %0d", m_addressed);
    $display("    configurations selected  %0d", m_configured);
    $display("    unconfigure requests ... %0d", m_unconfig);
    $display("    requests rejected ...... %0d", m_rejected);
    $display("    setup failures ......... %0d", m_setupfail);
    $display("");
    $display("  directed checks ........ %0d", chk_dir);
    $display("  random checks .......... %0d", chk_rnd);
    $display("  TOTAL checks ........... %0d", chk_dir + chk_rnd);
    $display("  ERRORS ................. %0d", err);
    if (err == 0) $display("  PASS"); else $display("  FAIL");
    $finish;
  end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                          VERILOG   SYSTEMVERILOG   VHDL-2008
    phase 1 intent          2,854         2,854        2,854
    phase 2 exhaustive      4,978         4,978        4,978
    phase 3 scenarios       5,330         5,330        5,330
    ---- DIRECTED           5,330         5,330        5,330
    errors                      0             0            0
    TOTAL                  56,400        56,400       56,532

    measured reachability, Verilog run
      cycles attached ..................... 4,369
      cycles configured ..................... 533
      cycles with the function enabled ...... 533
      bus resets ............................ 431
      addresses assigned .................... 776
      configurations selected ............... 195
      unconfigure requests .................. 115
      requests rejected ..................... 660
      setup failures .......................... 0

Three rows carry the argument.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    4,369 attached  vs  533 configured

      The device is attached for eight times as many cycles as it is
      usable. The belief's model of USB has no vocabulary for the other
      3,836 cycles.

    533 configured  ==  533 function-enabled

      The two counters are EQUAL, not approximately but exactly -- and
      they are equal in the VHDL run too, at its own value of 771,
      where the independent stimulus reached the configured state more
      often. The VALUE varies with the stimulus; the EQUALITY does not.
      That equality is the invariant, measured rather than asserted.

    431 bus resets

      Each one took a device from wherever it was back to DEFAULT.
      Prediction 3 said this would not matter.

The exhaustive sweep, and its named axes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    AXES
      device state     DEFAULT, ADDRESS, CONFIGURED              3
      request          none, SET_ADDRESS, SET_CONFIGURATION,
                       bus reset                                 4
      value class      zero, valid non-zero, out of range        3
      ------------------------------------------------------------
                                          3 x 4 x 3 =           36

All 36 reached, and each one was reached by driving the device there through the protocol — setup_state performs an attach, then a SET_ADDRESS, then a SET_CONFIGURATION, and checks that it arrived before the sweep body runs.

8. SystemVerilog

usb_fn_enable_sv.sv — the design, SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_fn_enable_sv -- the same contract in SystemVerilog. Same ports,
//  same states, same gate, same reset, same latency.
//
//  The device state is an ENUM here, so the three states are three
//  values and there is no fourth to fall into -- and the SVA block
//  states the gate itself as a property that E-M1 breaks.
//
//  ------------------------------------------------------------------
//  The difference between being plugged in and being
//  usable, as a state machine.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL. It is NOT an
//  enumeration engine: there is no control-transfer machine, no
//  descriptor store, no SETUP decoding, no data stage. The requests
//  arrive here already decoded, as two pulses with a value each, and
//  what the module does with them is the whole subject.
//
//  THE FIVE THINGS THAT ARE NOT THE SAME THING
//  -------------------------------------------
//      attached     there is a device on the wire and the host can see
//                   it. Nothing else.
//      DEFAULT      it has been reset and answers at address zero.
//      ADDRESS      it has been given an address of its own.
//      CONFIGURED   a configuration has been selected, so the
//                   endpoints the descriptors describe now exist.
//      usable       the function can move data -- which requires the
//                   line above it, and nothing less.
//
//  THE INVARIANT
//      fn_ep_en |-> (dev_state == ST_CONFIGURED)
//
//  Endpoint zero is different and it is the reason the whole scheme
//  works: it is enabled as soon as the device is attached, because it
//  is the channel over which everything else is arranged. Every other
//  endpoint is gated. Mutation E-M1 replaces the gate with `attached`,
//  which is the misconception written as RTL.
// =====================================================================
module usb_fn_enable_sv (
  input  logic       clk,
  input  logic       rst_n,

  // The device is electrically present and the host has seen it.
  input  logic       attached,
  // One cycle: the host drove a bus reset.
  input  logic       bus_reset,

  // Already-decoded standard requests.
  input  logic       set_addr_valid,
  input  logic [6:0] set_addr,
  input  logic       set_cfg_valid,
  input  logic [7:0] set_cfg,

  // How many configurations the descriptors declare. A request for one
  // that does not exist is not a state change.
  input  logic [7:0] n_configs,

  output logic [1:0] dev_state,
  output logic [6:0] dev_addr,
  output logic [7:0] cfg_value,

  // Endpoint zero: available from attachment, because it is how the
  // rest of this is negotiated.
  output logic       ep0_en,
  // Every other endpoint: available only when configured.
  output logic       fn_ep_en,

  output logic [15:0] n_resets,
  output logic [15:0] n_addressed,
  output logic [15:0] n_configured,
  output logic [15:0] n_rejected,
  // Cycles in which a non-control endpoint was enabled while the device
  // was not configured. Structurally impossible here; E-M1 makes it the
  // normal case.
  output logic [15:0] n_premature
);

  // The three device states as a type. There is no fourth value to
  // fall into, and a transition to one that does not exist is a
  // compile error rather than a default branch.
  typedef enum logic [1:0] {
    ST_DEFAULT    = 2'd0,
    ST_ADDRESS    = 2'd1,
    ST_CONFIGURED = 2'd2
  } dev_state_e;

  dev_state_e state;
  logic [6:0]  addr_r;
  logic [7:0]  cfg_r;
  logic [15:0] c_rst, c_addr, c_cfg, c_rej, c_prem;

  // SET_ADDRESS is defined in Default and Address. SET_CONFIGURATION is
  // defined in Address and Configured. A request outside its states is
  // a request error, not a state change -- 30.3 built the table.
  logic addr_legal;
  assign addr_legal = set_addr_valid && attached &&
                    ((state == ST_DEFAULT) || (state == ST_ADDRESS));
  logic cfg_legal;
  assign cfg_legal  = set_cfg_valid  && attached &&
                    ((state == ST_ADDRESS) || (state == ST_CONFIGURED)) &&
                    (set_cfg <= n_configs);

  logic req_rejected;
  assign req_rejected = (set_addr_valid && !addr_legal) ||
                      (set_cfg_valid  && !cfg_legal);

  assign ep0_en   = attached;
  assign fn_ep_en = attached && (state == ST_CONFIGURED);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state  <= ST_DEFAULT;
      addr_r <= 7'd0;
      cfg_r  <= 8'd0;
      c_rst  <= 16'd0; c_addr <= 16'd0; c_cfg <= 16'd0;
      c_rej  <= 16'd0; c_prem <= 16'd0;
    end else begin
      if (!attached) begin
        // Unplugging is not a protocol event the device negotiates. It
        // returns to the state it had before anybody knew it existed.
        state  <= ST_DEFAULT;
        addr_r <= 7'd0;
        cfg_r  <= 8'd0;
      end else if (bus_reset) begin
        // A bus reset returns the device to Default: address zero,
        // unconfigured. Everything the host arranged is undone, and
        // the host knows it because the host caused it.
        state  <= ST_DEFAULT;
        addr_r <= 7'd0;
        cfg_r  <= 8'd0;
        c_rst  <= c_rst + 16'd1;
      end else begin
        if (addr_legal) begin
          addr_r <= set_addr;
          // Address zero means "go back to answering as nobody".
          state  <= dev_state_e'((set_addr == 7'd0) ? ST_DEFAULT : ST_ADDRESS);
          if (set_addr != 7'd0) c_addr <= c_addr + 16'd1;
        end
        if (cfg_legal) begin
          cfg_r <= set_cfg;
          // Configuration zero means "unconfigure" -- a legal request
          // that takes a working device back to having no endpoints.
          state <= dev_state_e'((set_cfg == 8'd0) ? ST_ADDRESS : ST_CONFIGURED);
          if (set_cfg != 8'd0) c_cfg <= c_cfg + 16'd1;
        end
        if (req_rejected) c_rej <= c_rej + 16'd1;
      end

      if (fn_ep_en && (state != ST_CONFIGURED)) c_prem <= c_prem + 16'd1;
    end
  end

  assign dev_state    = state;
  assign dev_addr     = addr_r;
  assign cfg_value    = cfg_r;
  assign n_resets     = c_rst;
  assign n_addressed  = c_addr;
  assign n_configured = c_cfg;
  assign n_rejected   = c_rej;
  assign n_premature  = c_prem;


`ifdef SVA_ON
  // The gate as a property. Icarus rejects SVA; under Icarus the intent
  // checks in the testbench enforce each of these.

  // THE ONE. E-M1 replaces the gate with `attached` and this fails.
  property p_function_requires_configured;
    @(posedge clk) disable iff (!rst_n)
      fn_ep_en |-> (state == ST_CONFIGURED);
  endproperty
  a_function_requires_configured: assert property (p_function_requires_configured);

  // SAFETY. Endpoint zero is the exception, and its rule is simpler:
  // it exists whenever the device does.
  property p_ep0_follows_attachment;
    @(posedge clk) disable iff (!rst_n) ep0_en == attached;
  endproperty
  a_ep0_follows_attachment: assert property (p_ep0_follows_attachment);

  // SAFETY. A bus reset returns the device to Default, every time,
  // from every state.
  property p_reset_returns_to_default;
    @(posedge clk) disable iff (!rst_n)
      (attached && bus_reset) |=> (state == ST_DEFAULT) && (addr_r == '0);
  endproperty
  a_reset_returns_to_default: assert property (p_reset_returns_to_default);

  // SAFETY. Configuration cannot be reached from Default: the address
  // step is not optional either.
  property p_no_configure_from_default;
    @(posedge clk) disable iff (!rst_n)
      (state == ST_DEFAULT) |=> (state != ST_CONFIGURED);
  endproperty
  a_no_configure_from_default: assert property (p_no_configure_from_default);

  // PROGRESS. The sequence CAN be completed -- the gate is a gate, not
  // a wall. Without this, a design that never configured would satisfy
  // every safety property above.
  property p_configuration_is_reachable;
    @(posedge clk) disable iff (!rst_n)
      (attached && (state == ST_ADDRESS) && cfg_legal && (set_cfg != '0))
        |=> (state == ST_CONFIGURED) && fn_ep_en;
  endproperty
  a_configuration_is_reachable: assert property (p_configuration_is_reachable);

  c_default:    cover property (@(posedge clk) attached && state == ST_DEFAULT);
  c_address:    cover property (@(posedge clk) state == ST_ADDRESS);
  c_configured: cover property (@(posedge clk) state == ST_CONFIGURED);
  c_unconfig:   cover property (@(posedge clk) cfg_legal && set_cfg == '0);
  c_reject:     cover property (@(posedge clk) req_rejected);
  c_unplug_cfg: cover property (@(posedge clk) !attached && $past(fn_ep_en));
`endif


endmodule

tb_usb_fn_enable_sv.sv — the testbench, SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  tb_usb_fn_enable -- Verilog-2005 testbench for usb_fn_enable.
//
//  PHASE 1 is the intent phase and it consults no model. It states the
//  claim the chapter exists for and requires it every cycle:
//
//      a non-control endpoint is NEVER enabled unless the device is
//      configured -- no matter how long it has been plugged in
//
//  PHASES
//    1 INTENT      the gate, asserted directly, across a long attach
//    2 EXHAUSTIVE  every (state, request, value class) triple
//    3 SCENARIO    the full enumeration walk, and the ways back down
//    4 RANDOM      supplementary, audited
// =====================================================================
`timescale 1ns/1ps

module tb_usb_fn_enable_sv;

  localparam [1:0] ST_DEFAULT = 2'd0, ST_ADDRESS = 2'd1, ST_CONFIGURED = 2'd2;

  logic     clk = 1'b0;
  logic     rst_n, attached, bus_reset;
  logic     set_addr_valid, set_cfg_valid;
  logic [6:0] set_addr;
  logic [7:0] set_cfg, n_configs;

  wire [1:0] dev_state;
  wire [6:0] dev_addr;
  wire [7:0] cfg_value;
  wire       ep0_en, fn_ep_en;
  wire [15:0] n_resets, n_addressed, n_configured, n_rejected, n_premature;

  usb_fn_enable_sv dut (
    .clk(clk), .rst_n(rst_n), .attached(attached), .bus_reset(bus_reset),
    .set_addr_valid(set_addr_valid), .set_addr(set_addr),
    .set_cfg_valid(set_cfg_valid), .set_cfg(set_cfg),
    .n_configs(n_configs),
    .dev_state(dev_state), .dev_addr(dev_addr), .cfg_value(cfg_value),
    .ep0_en(ep0_en), .fn_ep_en(fn_ep_en),
    .n_resets(n_resets), .n_addressed(n_addressed),
    .n_configured(n_configured), .n_rejected(n_rejected),
    .n_premature(n_premature)
  );

  always #5 clk = ~clk;

  // ---- the independent reference model ---------------------------
  // The RULES, in words, then code:
  //   R1 unplugged is Default, address zero, unconfigured
  //   R2 a bus reset is the same, and is counted
  //   R3 SET_ADDRESS is defined in Default and Address only
  //   R4 SET_CONFIGURATION is defined in Address and Configured only,
  //      and only for a configuration that exists
  //   R5 value zero means "go back one step" in both cases
  logic [1:0]  rm_state;
  logic [6:0]  rm_addr;
  logic [7:0]  rm_cfg;
  logic [15:0] rm_rst, rm_adr, rm_cfgc, rm_rej;

  int  chk_dir, chk_rnd, err;
  bit  in_random;
  int  m_attach_cycles, m_cfg_cycles, m_fn_cycles, m_resets,
       m_addressed, m_configured, m_rejected, m_unconfig, m_setupfail;
  int  i, j, st, rq, vc;

  task bump; begin
    if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
  end endtask

  task ck(string what, logic [31:0] got, logic [31:0] exp);
    begin
      bump;
      if (got !== exp) begin
        err = err + 1;
        if (!in_random && err <= 40)
          $display("  ** %s: got %0d expected %0d  (t=%0t)", what, got, exp, $time);
      end
    end
  endtask

  task ref_step;
    logic a_ok, c_ok;
    begin
      if (!rst_n) begin
        rm_state = ST_DEFAULT; rm_addr = 0; rm_cfg = 0;
        rm_rst = 0; rm_adr = 0; rm_cfgc = 0; rm_rej = 0;
      end else if (!attached) begin
        rm_state = ST_DEFAULT; rm_addr = 0; rm_cfg = 0;   // R1
      end else if (bus_reset) begin
        rm_state = ST_DEFAULT; rm_addr = 0; rm_cfg = 0;   // R2
        rm_rst = rm_rst + 1;
        m_resets = m_resets + 1;
      end else begin
        a_ok = set_addr_valid &&
               ((rm_state == ST_DEFAULT) || (rm_state == ST_ADDRESS));   // R3
        c_ok = set_cfg_valid &&
               ((rm_state == ST_ADDRESS) || (rm_state == ST_CONFIGURED)) &&
               (set_cfg <= n_configs);                                   // R4
        if (a_ok) begin
          rm_addr  = set_addr;
          rm_state = (set_addr == 0) ? ST_DEFAULT : ST_ADDRESS;          // R5
          if (set_addr != 0) begin
            rm_adr = rm_adr + 1; m_addressed = m_addressed + 1;
          end
        end
        if (c_ok) begin
          rm_cfg   = set_cfg;
          rm_state = (set_cfg == 0) ? ST_ADDRESS : ST_CONFIGURED;        // R5
          if (set_cfg != 0) begin
            rm_cfgc = rm_cfgc + 1; m_configured = m_configured + 1;
          end else m_unconfig = m_unconfig + 1;
        end
        if ((set_addr_valid && !a_ok) || (set_cfg_valid && !c_ok)) begin
          rm_rej = rm_rej + 1; m_rejected = m_rejected + 1;
        end
      end
      if (attached) m_attach_cycles = m_attach_cycles + 1;
      if (rm_state == ST_CONFIGURED) m_cfg_cycles = m_cfg_cycles + 1;
    end
  endtask

  task cmp; begin
    ck("dev_state",    {30'd0, dev_state},   {30'd0, rm_state});
    ck("dev_addr",     {25'd0, dev_addr},    {25'd0, rm_addr});
    ck("cfg_value",    {24'd0, cfg_value},   {24'd0, rm_cfg});
    ck("ep0_en",       {31'd0, ep0_en},      {31'd0, attached});
    ck("fn_ep_en",     {31'd0, fn_ep_en},
                       {31'd0, (attached && (rm_state == ST_CONFIGURED))});
    ck("n_resets",     {16'd0, n_resets},    {16'd0, rm_rst});
    ck("n_addressed",  {16'd0, n_addressed}, {16'd0, rm_adr});
    ck("n_configured", {16'd0, n_configured},{16'd0, rm_cfgc});
    ck("n_rejected",   {16'd0, n_rejected},  {16'd0, rm_rej});
    ck("n_premature",  {16'd0, n_premature}, 32'd0);
    if (fn_ep_en) m_fn_cycles = m_fn_cycles + 1;
  end endtask

  // The claim, asserted directly, every cycle of every phase.
  task intent_check; begin
    bump;
    if (fn_ep_en && (dev_state != ST_CONFIGURED)) begin
      err = err + 1;
      $display("  ** INTENT VIOLATED: function endpoints enabled in state %0d (t=%0t)",
               dev_state, $time);
    end
    bump;
    if (fn_ep_en && !attached) begin
      err = err + 1;
      $display("  ** INTENT VIOLATED: function endpoints enabled while detached (t=%0t)",
               $time);
    end
  end endtask

  task step; begin
    #1;
    @(posedge clk);
    ref_step;
    #1;
    cmp;
    intent_check;
    bus_reset = 0; set_addr_valid = 0; set_cfg_valid = 0;
    set_addr = 0; set_cfg = 0;
  end endtask

  task idle; step; endtask

  task hard_reset; begin
    rst_n = 0; attached = 0; bus_reset = 0;
    set_addr_valid = 0; set_addr = 0; set_cfg_valid = 0; set_cfg = 0;
    n_configs = 8'd2;
    repeat (3) begin @(posedge clk); ref_step; end
    #1; rst_n = 1;
    @(posedge clk); ref_step; #1; cmp;
  end endtask

  task plug;      attached = 1; step; endtask
  task unplug;    attached = 0; step; endtask
  task do_reset;  bus_reset = 1; step; endtask
  task addr_req(logic [6:0] a);
    set_addr_valid = 1; set_addr = a; step;
  endtask
  task cfg_req(logic [7:0] c);
    set_cfg_valid = 1; set_cfg = c; step;
  endtask

  // -----------------------------------------------------------------
  //  PHASE 1 -- THE GATE.
  //
  //  Plug the device in and leave it there. Two hundred cycles of being
  //  electrically present, visible to the host, with endpoint zero
  //  working the whole time -- and the function endpoints stay dark.
  //  If enumeration were optional, this is the interval in which the
  //  device would start working. It does not.
  // -----------------------------------------------------------------
  int  attached_run;

  task phase_intent;
    begin
      hard_reset;
      plug;
      ck("attached", {31'd0, attached}, 32'd1);
      ck("endpoint zero is up", {31'd0, ep0_en}, 32'd1);
      attached_run = 0;
      for (i = 0; i < 200; i = i + 1) begin
        idle;
        ck("still nothing usable", {31'd0, fn_ep_en}, 32'd0);
        ck("but EP0 still is",     {31'd0, ep0_en},   32'd1);
        attached_run = attached_run + 1;
      end
      ck("still in Default",      {30'd0, dev_state}, {30'd0, ST_DEFAULT});
      ck("still address zero",    {25'd0, dev_addr},  32'd0);
      ck("nothing premature",     {16'd0, n_premature}, 32'd0);
      // now do the work, and only then does the function exist
      addr_req(7'd5);
      ck("addressed, still not usable", {31'd0, fn_ep_en}, 32'd0);
      cfg_req(8'd1);
      ck("configured: now usable",      {31'd0, fn_ep_en}, 32'd1);
      bump;
      if (attached_run < 200) begin
        err = err + 1;
        $display("  ** intent: only %0d attached cycles", attached_run);
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 2 -- the exhaustive sweep.
  //
  //  AXES:
  //    device state       DEFAULT, ADDRESS, CONFIGURED              3
  //    request            none, SET_ADDRESS, SET_CONFIGURATION,
  //                       bus reset                                 4
  //    value class        zero, valid non-zero, out of range        3
  //    ---------------------------------------------------------------
  //                                        3 x 4 x 3 =             36
  //
  //  WHAT IS ABSENT: detachment, which is not a request and cannot be
  //  a value class. It is phase 3. An exhaustive sweep is exhaustive
  //  over the axes it has.
  // -----------------------------------------------------------------
  task setup_state(logic [1:0] want);
    begin
      hard_reset;
      plug;
      if (want >= ST_ADDRESS)    addr_req(7'd9);
      if (want == ST_CONFIGURED) cfg_req(8'd1);
      bump;
      if (dev_state !== want) begin
        err = err + 1; m_setupfail = m_setupfail + 1;
        $display("  ** setup: wanted state %0d, reached %0d", want, dev_state);
      end
    end
  endtask

  task phase_sweep;
    begin
      for (st = 0; st < 3; st = st + 1)
      for (rq = 0; rq < 4; rq = rq + 1)
      for (vc = 0; vc < 3; vc = vc + 1) begin
        setup_state(2'(st));
        case (rq)
          0: idle;
          1: addr_req((vc == 0) ? 7'd0 : (vc == 1) ? 7'd17 : 7'd126);
          2: cfg_req ((vc == 0) ? 8'd0 : (vc == 1) ? 8'd1  : 8'd9);
          3: do_reset;
        endcase
        idle;
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 3 -- the walk, and every way back down.
  // -----------------------------------------------------------------
  task phase_scenarios;
    begin
      // S1 the whole sequence, one step at a time, checking that the
      //    function is unusable at every step but the last.
      hard_reset;
      ck("S1 detached: EP0 down",  {31'd0, ep0_en},   32'd0);
      plug;
      ck("S1 attached: EP0 up",    {31'd0, ep0_en},   32'd1);
      ck("S1 attached: fn down",   {31'd0, fn_ep_en}, 32'd0);
      do_reset;
      ck("S1 reset: Default",      {30'd0, dev_state},{30'd0, ST_DEFAULT});
      ck("S1 reset: fn down",      {31'd0, fn_ep_en}, 32'd0);
      addr_req(7'd12);
      ck("S1 addressed",           {30'd0, dev_state},{30'd0, ST_ADDRESS});
      ck("S1 addressed: fn down",  {31'd0, fn_ep_en}, 32'd0);
      ck("S1 the address stuck",   {25'd0, dev_addr}, 32'd12);
      cfg_req(8'd1);
      ck("S1 configured",          {30'd0, dev_state},{30'd0, ST_CONFIGURED});
      ck("S1 NOW it is usable",    {31'd0, fn_ep_en}, 32'd1);

      // S2 a bus reset undoes all of it, from Configured, in one cycle.
      do_reset;
      ck("S2 back to Default",     {30'd0, dev_state},{30'd0, ST_DEFAULT});
      ck("S2 address gone",        {25'd0, dev_addr}, 32'd0);
      ck("S2 function gone",       {31'd0, fn_ep_en}, 32'd0);

      // S3 SET_CONFIGURATION(0) is a legal request that unconfigures.
      hard_reset; plug; addr_req(7'd3); cfg_req(8'd1);
      ck("S3 usable",              {31'd0, fn_ep_en}, 32'd1);
      cfg_req(8'd0);
      ck("S3 unconfigured",        {30'd0, dev_state},{30'd0, ST_ADDRESS});
      ck("S3 not usable",          {31'd0, fn_ep_en}, 32'd0);
      ck("S3 but still addressed", {25'd0, dev_addr}, 32'd3);

      // S4 SET_ADDRESS(0) returns to Default from Address.
      hard_reset; plug; addr_req(7'd3);
      addr_req(7'd0);
      ck("S4 back to Default",     {30'd0, dev_state},{30'd0, ST_DEFAULT});

      // S5 SET_CONFIGURATION before an address is a request error, and
      //    the device does NOT become usable by asking nicely.
      hard_reset; plug;
      cfg_req(8'd1);
      ck("S5 rejected",            {16'd0, n_rejected}, 32'd1);
      ck("S5 still Default",       {30'd0, dev_state},  {30'd0, ST_DEFAULT});
      ck("S5 still not usable",    {31'd0, fn_ep_en},   32'd0);

      // S6 a configuration that does not exist is refused.
      hard_reset; plug; addr_req(7'd4);
      cfg_req(8'd7);
      ck("S6 refused",             {16'd0, n_rejected}, 32'd1);
      ck("S6 still only addressed",{30'd0, dev_state},  {30'd0, ST_ADDRESS});

      // S7 unplugging a configured device takes everything with it.
      hard_reset; plug; addr_req(7'd6); cfg_req(8'd2);
      ck("S7 usable",              {31'd0, fn_ep_en},   32'd1);
      unplug;
      ck("S7 EP0 down",            {31'd0, ep0_en},     32'd0);
      ck("S7 function down",       {31'd0, fn_ep_en},   32'd0);
      ck("S7 Default again",       {30'd0, dev_state},  {30'd0, ST_DEFAULT});
      plug;
      ck("S7 replug is not resume",{31'd0, fn_ep_en},   32'd0);
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 4 -- random, audited.
  // -----------------------------------------------------------------
  task phase_random;
    int r;
    begin
      in_random = 1;
      hard_reset; plug;
      for (j = 0; j < 4000; j = j + 1) begin
        r = $urandom_range(99);
        if (r < 22)      addr_req(7'($urandom_range(127)));
        else if (r < 44) cfg_req(8'($urandom_range(3)));
        else if (r < 54) do_reset;
        else if (r < 60) begin unplug; plug; end
        else if (r < 64) begin
          n_configs = 8'($urandom_range(3));
          idle;
        end else idle;
      end
      in_random = 0;
    end
  endtask

  initial begin
    chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
    m_attach_cycles=0; m_cfg_cycles=0; m_fn_cycles=0; m_resets=0;
    m_addressed=0; m_configured=0; m_rejected=0; m_unconfig=0;
    m_setupfail=0;

    phase_intent;
    $display("  phase 1 intent      : %0d checks, %0d errors  (%0d attached cycles, unusable)",
             chk_dir, err, attached_run);
    phase_sweep;
    $display("  phase 2 exhaustive  : %0d checks, %0d errors  (36 combinations)", chk_dir, err);
    phase_scenarios;
    $display("  phase 3 scenarios   : %0d checks, %0d errors", chk_dir, err);
    $display("  ---- DIRECTED-ONLY  : %0d checks, %0d errors ----", chk_dir, err);
    phase_random;

    $display("");
    $display("  measured reachability (all phases)");
    $display("    cycles attached ........ %0d", m_attach_cycles);
    $display("    cycles configured ...... %0d", m_cfg_cycles);
    $display("    cycles function enabled  %0d", m_fn_cycles);
    $display("    bus resets ............. %0d", m_resets);
    $display("    addresses assigned ..... %0d", m_addressed);
    $display("    configurations selected  %0d", m_configured);
    $display("    unconfigure requests ... %0d", m_unconfig);
    $display("    requests rejected ...... %0d", m_rejected);
    $display("    setup failures ......... %0d", m_setupfail);
    $display("");
    $display("  directed checks ........ %0d", chk_dir);
    $display("  random checks .......... %0d", chk_rnd);
    $display("  TOTAL checks ........... %0d", chk_dir + chk_rnd);
    $display("  ERRORS ................. %0d", err);
    if (err == 0) $display("  PASS"); else $display("  FAIL");
    $finish;
  end

endmodule

The invariant is one property, and it is the shortest statement of this chapter anywhere in the module:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  property p_function_requires_configured;
    @(posedge clk) disable iff (!rst_n)
      fn_ep_en |-> (state == ST_CONFIGURED);
  endproperty

And three more, of which the last is the one people forget to write:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  property p_ep0_follows_attachment;
    @(posedge clk) disable iff (!rst_n) ep0_en == attached;
  endproperty

  property p_reset_returns_to_default;
    @(posedge clk) disable iff (!rst_n)
      (attached && bus_reset) |=> (state == ST_DEFAULT) && (addr_r == '0);
  endproperty

  property p_no_configure_from_default;
    @(posedge clk) disable iff (!rst_n)
      (state == ST_DEFAULT) |=> (state != ST_CONFIGURED);
  endproperty

p_reset_returns_to_default says nothing about fn_ep_en, and it does not need to — because p_function_requires_configured holds unconditionally, in every cycle, so a one-cycle window with the gate open on an unconfigured device is already excluded. That is worth noticing rather than assuming: had the gate property been written with an antecedent, this reset property would have left exactly that window unchecked, and 28.2 measured the same class of gap in a different context — a property about state is not a property about the outputs derived from it. Here the two properties cover each other, and the reason they do is that one of them has no antecedent at all.

p_no_configure_from_default is the address step, as an obligation. Mutation E-M2 breaks precisely it.

9. VHDL-2008

usb_fn_enable.vhd — the design, VHDL-2008

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  usb_fn_enable (VHDL-2008) -- the same contract. Same ports, same
--  states, same gate, same reset, same latency.
--
--  The device state is an enumerated type with three values and no
--  fourth, so "the state machine fell into an undefined state" is not
--  a sentence that can be written about this file. The gate itself --
--  fn_ep_en only in CONFIGURED -- is one concurrent assignment, and a
--  reviewer checking the chapter's claim reads that one line.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity usb_fn_enable is
  port (
    clk            : in  std_logic;
    rst_n          : in  std_logic;
    attached       : in  std_logic;
    bus_reset      : in  std_logic;
    set_addr_valid : in  std_logic;
    set_addr       : in  unsigned(6 downto 0);
    set_cfg_valid  : in  std_logic;
    set_cfg        : in  unsigned(7 downto 0);
    n_configs      : in  unsigned(7 downto 0);
    dev_state      : out unsigned(1 downto 0);
    dev_addr       : out unsigned(6 downto 0);
    cfg_value      : out unsigned(7 downto 0);
    ep0_en         : out std_logic;
    fn_ep_en       : out std_logic;
    n_resets       : out unsigned(15 downto 0);
    n_addressed    : out unsigned(15 downto 0);
    n_configured   : out unsigned(15 downto 0);
    n_rejected     : out unsigned(15 downto 0);
    n_premature    : out unsigned(15 downto 0)
  );
end entity usb_fn_enable;

architecture rtl of usb_fn_enable is
  type dev_state_t is (ST_DEFAULT, ST_ADDRESS, ST_CONFIGURED);
  signal state  : dev_state_t := ST_DEFAULT;
  signal addr_r : unsigned(6 downto 0) := (others => '0');
  signal cfg_r  : unsigned(7 downto 0) := (others => '0');
  signal c_rst, c_addr, c_cfg, c_rej, c_prem : unsigned(15 downto 0)
         := (others => '0');
  signal addr_legal, cfg_legal, req_rejected, fn_i : std_logic;
begin

  addr_legal <= '1' when (set_addr_valid = '1' and attached = '1' and
                          (state = ST_DEFAULT or state = ST_ADDRESS))
                else '0';
  cfg_legal  <= '1' when (set_cfg_valid = '1' and attached = '1' and
                          (state = ST_ADDRESS or state = ST_CONFIGURED) and
                          set_cfg <= n_configs)
                else '0';
  req_rejected <= '1' when ((set_addr_valid = '1' and addr_legal = '0') or
                            (set_cfg_valid = '1' and cfg_legal = '0'))
                  else '0';

  -- The gate. One line.
  fn_i     <= '1' when (attached = '1' and state = ST_CONFIGURED) else '0';
  fn_ep_en <= fn_i;
  ep0_en   <= attached;

  seq : process (clk, rst_n)
  begin
    if rst_n = '0' then
      state <= ST_DEFAULT;
      addr_r <= (others => '0');
      cfg_r  <= (others => '0');
      c_rst <= (others => '0'); c_addr <= (others => '0');
      c_cfg <= (others => '0'); c_rej <= (others => '0');
      c_prem <= (others => '0');
    elsif rising_edge(clk) then
      if attached = '0' then
        state  <= ST_DEFAULT;
        addr_r <= (others => '0');
        cfg_r  <= (others => '0');
      elsif bus_reset = '1' then
        state  <= ST_DEFAULT;
        addr_r <= (others => '0');
        cfg_r  <= (others => '0');
        c_rst  <= c_rst + 1;
      else
        if addr_legal = '1' then
          addr_r <= set_addr;
          if set_addr = 0 then
            state <= ST_DEFAULT;
          else
            state  <= ST_ADDRESS;
            c_addr <= c_addr + 1;
          end if;
        end if;
        if cfg_legal = '1' then
          cfg_r <= set_cfg;
          if set_cfg = 0 then
            state <= ST_ADDRESS;
          else
            state <= ST_CONFIGURED;
            c_cfg <= c_cfg + 1;
          end if;
        end if;
        if req_rejected = '1' then
          c_rej <= c_rej + 1;
        end if;
      end if;
      if fn_i = '1' and state /= ST_CONFIGURED then
        c_prem <= c_prem + 1;
      end if;
    end if;
  end process seq;

  dev_state    <= to_unsigned(dev_state_t'pos(state), 2);
  dev_addr     <= addr_r;
  cfg_value    <= cfg_r;
  n_resets     <= c_rst;
  n_addressed  <= c_addr;
  n_configured <= c_cfg;
  n_rejected   <= c_rej;
  n_premature  <= c_prem;

end architecture rtl;

tb_usb_fn_enable.vhd — the testbench, VHDL-2008

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  tb_usb_fn_enable -- VHDL-2008 testbench for usb_fn_enable.
--  Phases 1-3 present the SAME directed stimulus as the other two
--  benches, so their directed counts must agree.
--
--  Phase 1 states the gate directly: two hundred cycles of being
--  plugged in, with endpoint zero working, and the function endpoints
--  dark the whole time.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

entity tb_usb_fn_enable is
end entity tb_usb_fn_enable;

architecture sim of tb_usb_fn_enable is
  constant HALF : time := 10 ns;
  signal clk, rst_n, attached, bus_reset : std_logic := '0';
  signal set_addr_valid, set_cfg_valid   : std_logic := '0';
  signal set_addr  : unsigned(6 downto 0) := (others => '0');
  signal set_cfg   : unsigned(7 downto 0) := (others => '0');
  signal n_configs : unsigned(7 downto 0) := to_unsigned(2, 8);
  signal dev_state : unsigned(1 downto 0);
  signal dev_addr  : unsigned(6 downto 0);
  signal cfg_value : unsigned(7 downto 0);
  signal ep0_en, fn_ep_en : std_logic;
  signal n_resets, n_addressed, n_configured, n_rejected, n_premature
         : unsigned(15 downto 0);
  signal done_flag : boolean := false;

  function b2i (s : std_logic) return integer is
  begin
    if s = '1' then return 1; else return 0; end if;
  end function b2i;
begin

  dut : entity work.usb_fn_enable
    port map (clk => clk, rst_n => rst_n, attached => attached,
              bus_reset => bus_reset, set_addr_valid => set_addr_valid,
              set_addr => set_addr, set_cfg_valid => set_cfg_valid,
              set_cfg => set_cfg, n_configs => n_configs,
              dev_state => dev_state, dev_addr => dev_addr,
              cfg_value => cfg_value, ep0_en => ep0_en, fn_ep_en => fn_ep_en,
              n_resets => n_resets, n_addressed => n_addressed,
              n_configured => n_configured, n_rejected => n_rejected,
              n_premature => n_premature);

  clkgen : process
  begin
    while not done_flag loop
      clk <= '0'; wait for HALF;
      clk <= '1'; wait for HALF;
    end loop;
    wait;
  end process clkgen;

  stim : process
    constant ST_DEFAULT : natural := 0;
    constant ST_ADDRESS : natural := 1;
    constant ST_CONFIG  : natural := 2;

    variable rm_state : natural := 0;
    variable rm_addr  : natural := 0;
    variable rm_cfg   : natural := 0;
    variable rm_rst, rm_adr, rm_cfgc, rm_rej : natural := 0;

    variable chk_dir, chk_rnd, errs, shown : natural := 0;
    variable in_random : boolean := false;
    variable m_attach_cycles, m_cfg_cycles, m_fn_cycles, m_resets : natural := 0;
    variable m_addressed, m_configured, m_rejected, m_unconfig : natural := 0;
    variable m_setupfail, attached_run : natural := 0;
    variable seed1 : positive := 118_339; variable seed2 : positive := 90_071;

    procedure bump is
    begin
      if in_random then chk_rnd := chk_rnd + 1; else chk_dir := chk_dir + 1; end if;
    end procedure bump;

    procedure ck (what : string; got : integer; exp : integer) is
    begin
      bump;
      if got /= exp then
        errs := errs + 1;
        if (not in_random) and shown < 40 then
          shown := shown + 1;
          report "  ** " & what & ": got " & integer'image(got) &
                 " expected " & integer'image(exp) severity warning;
        end if;
      end if;
    end procedure ck;

    procedure ref_step is
      variable a_ok, c_ok : boolean;
    begin
      if rst_n = '0' then
        rm_state := ST_DEFAULT; rm_addr := 0; rm_cfg := 0;
        rm_rst := 0; rm_adr := 0; rm_cfgc := 0; rm_rej := 0;
      elsif attached = '0' then
        rm_state := ST_DEFAULT; rm_addr := 0; rm_cfg := 0;
      elsif bus_reset = '1' then
        rm_state := ST_DEFAULT; rm_addr := 0; rm_cfg := 0;
        rm_rst := rm_rst + 1; m_resets := m_resets + 1;
      else
        a_ok := (set_addr_valid = '1') and
                (rm_state = ST_DEFAULT or rm_state = ST_ADDRESS);
        c_ok := (set_cfg_valid = '1') and
                (rm_state = ST_ADDRESS or rm_state = ST_CONFIG) and
                (set_cfg <= n_configs);
        if a_ok then
          rm_addr := to_integer(set_addr);
          if set_addr = 0 then
            rm_state := ST_DEFAULT;
          else
            rm_state := ST_ADDRESS;
            rm_adr := rm_adr + 1; m_addressed := m_addressed + 1;
          end if;
        end if;
        if c_ok then
          rm_cfg := to_integer(set_cfg);
          if set_cfg = 0 then
            rm_state := ST_ADDRESS; m_unconfig := m_unconfig + 1;
          else
            rm_state := ST_CONFIG;
            rm_cfgc := rm_cfgc + 1; m_configured := m_configured + 1;
          end if;
        end if;
        if ((set_addr_valid = '1') and not a_ok) or
           ((set_cfg_valid = '1') and not c_ok) then
          rm_rej := rm_rej + 1; m_rejected := m_rejected + 1;
        end if;
      end if;
      if attached = '1' then m_attach_cycles := m_attach_cycles + 1; end if;
      if rm_state = ST_CONFIG then m_cfg_cycles := m_cfg_cycles + 1; end if;
    end procedure ref_step;

    procedure cmp is
      variable e_fn : integer;
    begin
      if attached = '1' and rm_state = ST_CONFIG then e_fn := 1; else e_fn := 0; end if;
      ck("dev_state",    to_integer(dev_state),    rm_state);
      ck("dev_addr",     to_integer(dev_addr),     rm_addr);
      ck("cfg_value",    to_integer(cfg_value),    rm_cfg);
      ck("ep0_en",       b2i(ep0_en),              b2i(attached));
      ck("fn_ep_en",     b2i(fn_ep_en),            e_fn);
      ck("n_resets",     to_integer(n_resets),     rm_rst);
      ck("n_addressed",  to_integer(n_addressed),  rm_adr);
      ck("n_configured", to_integer(n_configured), rm_cfgc);
      ck("n_rejected",   to_integer(n_rejected),   rm_rej);
      ck("n_premature",  to_integer(n_premature),  0);
      if fn_ep_en = '1' then m_fn_cycles := m_fn_cycles + 1; end if;
    end procedure cmp;

    procedure intent_check is
    begin
      bump;
      if fn_ep_en = '1' and to_integer(dev_state) /= ST_CONFIG then
        errs := errs + 1;
        report "  ** INTENT VIOLATED: function endpoints enabled unconfigured"
          severity warning;
      end if;
      bump;
      if fn_ep_en = '1' and attached = '0' then
        errs := errs + 1;
        report "  ** INTENT VIOLATED: function endpoints enabled while detached"
          severity warning;
      end if;
    end procedure intent_check;

    procedure step is
    begin
      wait for 1 ns;
      wait until rising_edge(clk);
      ref_step;
      wait for 1 ns;
      cmp;
      intent_check;
      bus_reset <= '0'; set_addr_valid <= '0'; set_cfg_valid <= '0';
      set_addr <= (others => '0'); set_cfg <= (others => '0');
    end procedure step;

    procedure idle is begin step; end procedure;

    procedure hard_reset is
    begin
      rst_n <= '0'; attached <= '0'; bus_reset <= '0';
      set_addr_valid <= '0'; set_addr <= (others => '0');
      set_cfg_valid <= '0'; set_cfg <= (others => '0');
      n_configs <= to_unsigned(2, 8);
      for i in 0 to 2 loop wait until rising_edge(clk); ref_step; end loop;
      wait for 1 ns; rst_n <= '1';
      wait until rising_edge(clk); ref_step; wait for 1 ns; cmp;
    end procedure hard_reset;

    procedure plug     is begin attached <= '1'; step; end procedure;
    procedure unplug   is begin attached <= '0'; step; end procedure;
    procedure do_reset is begin bus_reset <= '1'; step; end procedure;
    procedure addr_req (a : natural) is
    begin set_addr_valid <= '1'; set_addr <= to_unsigned(a, 7); step; end procedure;
    procedure cfg_req (c : natural) is
    begin set_cfg_valid <= '1'; set_cfg <= to_unsigned(c, 8); step; end procedure;

    procedure setup_state (want : natural) is
    begin
      hard_reset;
      plug;
      if want >= ST_ADDRESS then addr_req(9); end if;
      if want = ST_CONFIG   then cfg_req(1);  end if;
      bump;
      if to_integer(dev_state) /= want then
        errs := errs + 1; m_setupfail := m_setupfail + 1;
        report "  ** setup: state not reached" severity warning;
      end if;
    end procedure setup_state;

    impure function rnd (n : positive) return natural is
      variable x : real;
    begin
      uniform(seed1, seed2, x);
      return natural(real(n - 1) * x);
    end function rnd;

    variable r : natural;
  begin
    -- ---- PHASE 1 : the gate ----
    hard_reset;
    plug;
    ck("attached", b2i(attached), 1);
    ck("endpoint zero is up", b2i(ep0_en), 1);
    attached_run := 0;
    for i in 0 to 199 loop
      idle;
      ck("still nothing usable", b2i(fn_ep_en), 0);
      ck("but EP0 still is",     b2i(ep0_en),   1);
      attached_run := attached_run + 1;
    end loop;
    ck("still in Default",   to_integer(dev_state), ST_DEFAULT);
    ck("still address zero", to_integer(dev_addr),  0);
    ck("nothing premature",  to_integer(n_premature), 0);
    addr_req(5);
    ck("addressed, still not usable", b2i(fn_ep_en), 0);
    cfg_req(1);
    ck("configured: now usable", b2i(fn_ep_en), 1);
    bump;
    if attached_run < 200 then
      errs := errs + 1;
      report "  ** intent: too few attached cycles" severity warning;
    end if;
    report "  phase 1 intent      : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors  (" &
           integer'image(attached_run) & " attached cycles, unusable)";

    -- ---- PHASE 2 : 3 x 4 x 3 = 36 ----
    for st in 0 to 2 loop
      for rq in 0 to 3 loop
        for vc in 0 to 2 loop
          setup_state(st);
          case rq is
            when 0 => idle;
            when 1 =>
              if vc = 0 then addr_req(0);
              elsif vc = 1 then addr_req(17);
              else addr_req(126); end if;
            when 2 =>
              if vc = 0 then cfg_req(0);
              elsif vc = 1 then cfg_req(1);
              else cfg_req(9); end if;
            when others => do_reset;
          end case;
          idle;
        end loop;
      end loop;
    end loop;
    report "  phase 2 exhaustive  : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors  (36 combinations)";

    -- ---- PHASE 3 : the walk ----
    hard_reset;
    ck("S1 detached: EP0 down", b2i(ep0_en), 0);
    plug;
    ck("S1 attached: EP0 up",   b2i(ep0_en), 1);
    ck("S1 attached: fn down",  b2i(fn_ep_en), 0);
    do_reset;
    ck("S1 reset: Default",  to_integer(dev_state), ST_DEFAULT);
    ck("S1 reset: fn down",  b2i(fn_ep_en), 0);
    addr_req(12);
    ck("S1 addressed",          to_integer(dev_state), ST_ADDRESS);
    ck("S1 addressed: fn down", b2i(fn_ep_en), 0);
    ck("S1 the address stuck",  to_integer(dev_addr), 12);
    cfg_req(1);
    ck("S1 configured",       to_integer(dev_state), ST_CONFIG);
    ck("S1 NOW it is usable", b2i(fn_ep_en), 1);

    do_reset;
    ck("S2 back to Default", to_integer(dev_state), ST_DEFAULT);
    ck("S2 address gone",    to_integer(dev_addr), 0);
    ck("S2 function gone",   b2i(fn_ep_en), 0);

    hard_reset; plug; addr_req(3); cfg_req(1);
    ck("S3 usable", b2i(fn_ep_en), 1);
    cfg_req(0);
    ck("S3 unconfigured",       to_integer(dev_state), ST_ADDRESS);
    ck("S3 not usable",         b2i(fn_ep_en), 0);
    ck("S3 but still addressed",to_integer(dev_addr), 3);

    hard_reset; plug; addr_req(3);
    addr_req(0);
    ck("S4 back to Default", to_integer(dev_state), ST_DEFAULT);

    hard_reset; plug;
    cfg_req(1);
    ck("S5 rejected",         to_integer(n_rejected), 1);
    ck("S5 still Default",    to_integer(dev_state), ST_DEFAULT);
    ck("S5 still not usable", b2i(fn_ep_en), 0);

    hard_reset; plug; addr_req(4);
    cfg_req(7);
    ck("S6 refused",              to_integer(n_rejected), 1);
    ck("S6 still only addressed", to_integer(dev_state), ST_ADDRESS);

    hard_reset; plug; addr_req(6); cfg_req(2);
    ck("S7 usable", b2i(fn_ep_en), 1);
    unplug;
    ck("S7 EP0 down",      b2i(ep0_en), 0);
    ck("S7 function down", b2i(fn_ep_en), 0);
    ck("S7 Default again", to_integer(dev_state), ST_DEFAULT);
    plug;
    ck("S7 replug is not resume", b2i(fn_ep_en), 0);

    report "  phase 3 scenarios   : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors";
    report "  ---- DIRECTED-ONLY  : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors ----";

    -- ---- PHASE 4 : random ----
    in_random := true;
    hard_reset; plug;
    for j in 0 to 3999 loop
      r := rnd(100);
      if r < 22 then      addr_req(rnd(128));
      elsif r < 44 then   cfg_req(rnd(4));
      elsif r < 54 then   do_reset;
      elsif r < 60 then   unplug; plug;
      elsif r < 64 then
        n_configs <= to_unsigned(rnd(4), 8);
        idle;
      else                idle;
      end if;
    end loop;
    in_random := false;

    report "  measured reachability (all phases)";
    report "    cycles attached ........ " & integer'image(m_attach_cycles);
    report "    cycles configured ...... " & integer'image(m_cfg_cycles);
    report "    cycles function enabled  " & integer'image(m_fn_cycles);
    report "    bus resets ............. " & integer'image(m_resets);
    report "    addresses assigned ..... " & integer'image(m_addressed);
    report "    configurations selected  " & integer'image(m_configured);
    report "    unconfigure requests ... " & integer'image(m_unconfig);
    report "    requests rejected ...... " & integer'image(m_rejected);
    report "    setup failures ......... " & integer'image(m_setupfail);
    report "  directed checks ........ " & integer'image(chk_dir);
    report "  random checks .......... " & integer'image(chk_rnd);
    report "  TOTAL checks ........... " & integer'image(chk_dir + chk_rnd);
    report "  ERRORS ................. " & integer'image(errs);
    if errs = 0 then report "  PASS"; else report "  FAIL" severity failure; end if;
    done_flag <= true;
    wait;
  end process stim;

end architecture sim;

VHDL gives the five-line hierarchy a name per line, which for this subject is worth more than it usually is: an enumerated dev_state_t makes a report say ST_ADDRESS rather than 1, and a reviewer asking "which states enable the function" reads one concurrent assignment.

10. The Misconception As Hardware

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    MUT    THE BELIEF ENCODED                      V-DIR  SV-DIR  VH-DIR
    E-M1   enumeration is optional -- attached is
           enough (fn_ep_en = attached)            1,246   1,246   1,246
    E-M2   the address step is a formality
           (SET_CONFIGURATION accepted from
            DEFAULT, skipping ADDRESS)                22      22      22

BASE zero in all six columns; directed columns identical across all three languages.

E-M1 is one term deleted:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    assign fn_ep_en = attached && (state == ST_CONFIGURED);  // architecture
    assign fn_ep_en = attached;                              // the belief

It scores 1,246 — the highest of any mutation in this module — because a device built this way is wrong in every cycle in which it is attached and not configured, and that is most of its life. It also starts n_premature counting — the output that exists precisely so that a build in which the impossible became possible would say so, and which reads zero in every correct run of all three languages.

E-M2 scores 22, and its low score is the informative part. It accepts SET_CONFIGURATION from DEFAULT — skipping the address step entirely — which is wrong in exactly the cycles where somebody tries it. The directed suite tries it 22 times because a scenario was written to try it; a bench that only ever enumerated in the correct order would score zero and report a clean pass.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    E-M1  wrong everywhere      caught by everything
    E-M2  wrong only when a
          specific illegal
          sequence is attempted caught only because someone attempted it

    A suite that only exercises the LEGAL sequence cannot distinguish a
    device that enforces ordering from one that does not.

11. What The Wrong Model Does To Debugging

This is the chapter's real subject, because the belief costs weeks and never produces a single wrong bit.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    SYMPTOM      "the device does nothing"

    WRONG MODEL  attached means usable; enumeration is housekeeping
    WRONG QUESTION   what is wrong with my endpoint?
    WASTED ON        the data path, the buffers, the FIFO, the function
                     logic, the firmware's transmit routine -- all of
                     which are typically CORRECT, because they were
                     verified, and none of which has been reached

    CORRECT MODEL  a function endpoint exists only in the CONFIGURED
                   state
    THE FIRST QUESTION, and it is one observation:
                 WHAT STATE DID THE DEVICE REACH?

    and the four answers are four unrelated investigations:

      never attached / no reset seen  -> electrical. Power, D+/D-,
                                         pull-up, connector, VBUS.
                                         Not a logic problem at all.

      reached DEFAULT, stuck there    -> endpoint 0 is not answering
                                         correctly. The control
                                         machine, the SETUP decode, or
                                         the device descriptor.

      reached ADDRESS, stuck there    -> the host read your
                                         configuration descriptors and
                                         declined. A DESCRIPTOR defect:
                                         wLength, a size mismatch, an
                                         endpoint count, requested
                                         bandwidth the host cannot
                                         grant.

      reached CONFIGURED, no data     -> NOW it is your endpoint, and
                                         31.2's tree applies: is the
                                         host issuing tokens to it?

One observation. Four branches. Three of them are not in the data path, and the belief suggests looking only at the data path.

And the bring-up consequence, which follows from prediction 4 and is the most practical sentence in this chapter: you cannot stub enumeration to test the data path. There is no legal sequence in which a function endpoint receives a token without a configuration having been selected. An engineer who plans bring-up as "get the endpoint working, then do the enumeration bit" has planned the two phases in an impossible order.

12. Interview Reasoning

"A device is plugged in and nothing happens. Where do you start?"

I would start by establishing what state the device reached, because "nothing happens" has at least four unrelated causes and they are distinguished by one observation rather than by guessing.

The reason is that being attached and being usable are different things, with three steps between them. Electrically present is the first. Then a bus reset puts the device in Default at address 0, where its only channel is endpoint 0. Then SET_ADDRESS gives it an address. Then the host reads its configuration descriptors and issues SET_CONFIGURATION — and only then do the endpoints the descriptors declare exist. A function endpoint cannot carry a byte before that point; a token to it isn't a transfer, it's an error.

So the four branches. If no reset is ever seen, it is electrical — VBUS, the pull- up, the connector — and not a logic problem. If it reaches Default and sticks, the control endpoint or the device descriptor is at fault. If it reaches Address and sticks, the host read the configuration descriptors and declined them — that is a descriptor defect, and it is the one people misdiagnose, because the data path is perfect and has never been reached. If it reaches Configured and still no data, now it is the endpoint, and the first question there is whether the host is polling it at all.

Two things I would add because they catch people out. "It enumerated" is not one event — a device that reached Address appears in the host's device list, which reads as success. And a bus reset returns the device to Default and destroys the configuration, so every non-control endpoint stops existing; a device that treats its address as permanent works until the first resume.

13. Exercises

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  THE FIVE LINES
       Write out the five things that are not the same thing, and give
       an observable symptom for a device stuck at each one.

    2  THE EXCEPTION
       Explain why endpoint 0 must be enabled from attachment, and
       describe what would be impossible if it were gated on
       CONFIGURED.

    3  BUS RESET
       List everything a bus reset destroys and everything it leaves
       alone. Which of these is a chip reset also responsible for?

    4  VERILOG
       Add the SUSPEND state. Which outputs does it gate, and is a
       resume a bus reset?

    5  SYSTEMVERILOG
       Write the property that says an address, once assigned, survives
       a SET_CONFIGURATION and does not survive a bus reset.

    6  VHDL
       Implement exercise 4 and say what the enumerated state type gave
       you in the failure reports.

    7  TESTBENCH
       Phase 1 holds the device attached and unusable for 200 cycles.
       Write the equivalent negative intent check for the SUSPEND state
       in exercise 4.

    8  MUTATION
       E-M2 scores 22 only because a scenario attempts an illegal
       sequence. Write a third mutation that a legal-sequences-only
       bench would score ZERO on, and say what that proves about such a
       bench.

    9  DEBUG
       A device works on Linux and not on one Windows machine, and
       enumerates on both. Give the observation that distinguishes the
       cases, and say which of the four branches each outcome puts you
       in.

   10  BRING-UP
       Write the bring-up order for a new USB device, and justify why
       the data path is not first.

14. What Carries Forward

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE CORRECTION
    o  enumeration does not REGISTER a device's functionality, it
       CREATES it
    o  five things that are not the same thing: attached, DEFAULT,
       ADDRESS, CONFIGURED, usable -- each requiring the one above
    o  endpoint 0 is enabled from attachment because it is the channel
       over which the rest is arranged; every other endpoint is gated
    o  a bus reset returns the device to DEFAULT and every non-control
       endpoint stops existing -- and a bus reset is a routine event
    o  descriptors do not describe, they DECLARE: a device with a
       perfect data path and one wrong descriptor byte moves no data at
       all
    o  you cannot stub enumeration to bring up the data path. There is
       no legal sequence in which a function endpoint is addressed
       before a configuration is selected.

    THE HARDWARE
    o  fn_ep_en = attached && (state == CONFIGURED), and ep0_en =
       attached. The asymmetry is the architecture.
    o  an n_premature counter that is structurally zero, so that a
       build in which it is not says so
    o  SET_CONFIGURATION(0) unconfigures and is not an error;
       SET_ADDRESS while configured is rejected

    THE METHOD
    o  a negative architectural claim ("attached is not usable") has to
       be an intent check held over TIME -- 200 cycles -- because no
       comparison against a belief-sharing model can express it
    o  reach a state the way the design promises, never by forcing, and
       have the bench CHECK that it arrived: m_setupfail == 0 is
       load-bearing
    o  a property about state is not a property about the outputs
       derived from it; the reset property checks both
    o  a suite that only exercises the LEGAL sequence cannot tell a
       device that enforces ordering from one that does not -- E-M2
       would score zero
    o  the misconception is wrong everywhere (1,246), so removing its
       dedicated phase leaves 437. Nobody ships this belief; they debug
       with it.

    THE DEBUG CONSEQUENCE
    o  "the device does nothing" -> ask WHAT STATE IT REACHED. One
       observation, four branches, three of them outside the data path.
    o  "it enumerated" is not one event. Reached ADDRESS still appears
       in the device list.

One belief remains, and it is the only one in this module that is newer than the engineers who hold it — which is why it is the one most likely to be repeated in a design review this year.

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.