Skip to content
VLSI Mentor

USB · Module 22

Host Resource Management

Software cannot edit a context hardware owns, so xHCI has two of them — and inside a Configure Endpoint command the Drop flags are applied before the Add flags, making drop+add an atomic re-initialisation.

22.2 solved ring ownership with one bit. This chapter is the same problem one level up: who owns the description of an endpoint, and how it changes hands.

1. Software Does Not Own This State

Every endpoint on every device has a context: a structure in host memory holding its type, its max packet size, its ring pointer and its current state.

The controller reads it — and writes it. The state field in particular is hardware's: updated as transfers complete, as endpoints stall, as errors halt them.

So software cannot simply edit it. A context hardware owns may be written by hardware at any moment, and a read-modify-write from software would silently discard whatever hardware wrote in between. There is no lock, no compare-and-swap, and no way to make one across a PCIe link.

2. Add and Drop Flags

The Input Context begins with a control section holding two 32-bit masks:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   Drop Context flags   D31 .. D1  D0      endpoints to TEAR DOWN
   Add  Context flags   A31 .. A1  A0      endpoints to SET UP

   bit 1  = endpoint 0 (the control endpoint)
   bit 2  = the next endpoint, and so on
   bit 0  = the SLOT context, not an endpoint

And the rule that makes the pair useful:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   DROP FLAGS ARE APPLIED BEFORE ADD FLAGS.

Setting both bits for the same endpoint in one command is therefore not a contradiction. It is a re-initialisation: tear the endpoint down and build it again, atomically, in one command, with no window in which the endpoint does not exist.

Software uses it to change an endpoint's max packet size or its ring pointer — things that cannot be edited in place, because hardware owns the context — without a teardown that another thread could observe.

Bit 0 of the Drop mask would mean "drop the slot context" — and a Configure Endpoint command may not do that. The slot is torn down by Disable Slot, not by a configuration change.

So D0 is ignored. A controller that honours it destroys the device's addressing during a routine reconfiguration: the slot context holds the device address, the route string and the hub topology, and without it the device is unreachable until it is re-enumerated.

The design reports it as illegal_drop rather than merely ignoring it, because a driver setting D0 has a bug, and a bug that is silently absorbed is a bug that ships.

4. A Doorbell Is a Hint, Not a Command

Ringing an endpoint's doorbell means "there may be new work on your ring." It is not an instruction to start.

A doorbell for an endpoint that is DISABLED, or HALTED and not yet reset, is perfectly legal and does nothing. Software is allowed to be optimistic — it rings after enqueueing, without first reading back a state that hardware owns — and hardware re-checks rather than assuming.

Four states, and the fact that three of them are 'not transferring'

A four-state endpoint context machine. A Configure Endpoint Add flag moves DISABLED to STOPPED. A doorbell moves STOPPED to RUNNING. A transfer error moves RUNNING to HALTED. A Reset Endpoint command moves HALTED back to STOPPED. A Drop flag returns any configured state to DISABLED.DISABLEDSTOPPEDRUNNINGHALTEDConfigure: AddConfigure:AddConfigure: DropConfigure: Dropdoorbelldoorbelltransfer errortransfererrorReset EndpointResetEndpointtransfer errortransfererror
DISABLED, STOPPED and HALTED all mean no data is moving, and each needs a completely different action from software. Ringing a doorbell at a halted endpoint is legal, common, and does nothing — which is why the doorbell is a hint.

A controller that treats the doorbell as a command starts a halted endpoint without the Reset Endpoint that was supposed to clear the error — and the first thing it does is fail again, in exactly the same way, for ever. Mutation P3.

5. What We Are Building

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  xhci_ep_context        -- ONE endpoint's context

  commands                      state
  --------                      -----
  cfg_valid                     state    DISABLED / STOPPED /
  my_drop   (this context's D)           RUNNING / HALTED
  my_add    (this context's A)  usable
  is_slot_ctx  (index 0)        reinitialised  dropped AND added
                                illegal_drop   D0 was set
  db_valid / db_is_mine         db_ignored     a hint that did nothing
  ep_error
  reset_ep

  n_configured / n_disabled / n_reinit
  n_db_ignored / n_illegal_drop / n_halts

The priority chain is itself the specification:

PriorityInputWhy it outranks what follows
1cfg_valida command says whether the endpoint exists; a doorbell is a hint about its ring
2ep_errorthe endpoint has already failed; a doorbell cannot undo that
3reset_epclears a halt, and only a halt
4db_validthe hint, applied last and only if nothing else happened

6. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// xhci_ep_context -- the per-endpoint context an xHCI host controller owns,
// and the ordering rule inside a Configure Endpoint command that makes
// "drop it and add it" mean something rather than nothing.
//
// SOFTWARE DOES NOT OWN THIS STATE
//
// Every endpoint on every device has a CONTEXT: a structure in host memory
// holding its type, its packet size, its ring pointer and its current state.
// The controller reads and WRITES it -- the state field in particular is
// hardware's, updated as transfers complete, stall, or error out.
//
// So software cannot simply edit it. A context hardware owns may be written
// by hardware at any moment, and a read-modify-write from software would
// silently discard whatever hardware wrote in between.
//
// xHCI solves this by having TWO of them. The OUTPUT context is hardware's
// and software only reads it. The INPUT context is software's scratch copy,
// and software hands it over with a command:
//
//     software fills an INPUT context
//     software issues Configure Endpoint, pointing at it
//     hardware reads it, applies it, and updates the OUTPUT context
//
// No shared mutable structure, no read-modify-write race, and one clearly
// identified moment of transfer. That is the same shape as the cycle bit in
// chapter 22.2: ownership expressed structurally rather than by convention.
//
// THE INPUT CONTROL CONTEXT: ADD AND DROP FLAGS
//
// The Input Context begins with a control section holding two 32-bit masks:
//
//     Drop Context flags  D31..D0   endpoints to TEAR DOWN
//     Add  Context flags  A31..A0   endpoints to SET UP
//
// Bit 1 is endpoint 0, bit 2 the next, and so on. Bit 0 refers to the SLOT
// context rather than to an endpoint.
//
// And the rule that makes the pair useful:
//
//     DROP FLAGS ARE APPLIED BEFORE ADD FLAGS.
//
// Setting both bits for the same endpoint in one command is therefore not a
// contradiction. It is a RE-INITIALISATION: tear the endpoint down and build
// it again, atomically, in one command, with no window in which the endpoint
// does not exist. Software uses it to change an endpoint's packet size or
// ring pointer without a teardown that another thread could observe.
//
// Apply Add first and the same command means the exact opposite -- set it up,
// then tear it down -- and the endpoint ends up DISABLED. Same bits, same
// command, and the device stops working.
//
// D0 IS NOT A LEGAL DROP
//
// Bit 0 of the DROP mask would mean "drop the slot context", and a Configure
// Endpoint command may not do that -- the slot is torn down by Disable Slot,
// not by a configuration change. So D0 is ignored, and a controller that
// honours it destroys the device's addressing on a routine reconfiguration.
//
// A DOORBELL IS A HINT, NOT A COMMAND
//
// Ringing an endpoint's doorbell means "there may be new work on your ring".
// It is not an instruction to start. A doorbell for an endpoint that is
// DISABLED, or HALTED and not yet reset, is perfectly legal and does
// NOTHING -- software is allowed to be optimistic, and hardware re-checks
// rather than assuming.
//
// A controller that treats the doorbell as a command starts a halted
// endpoint without the reset that was supposed to clear the error, and the
// first thing it does is fail again in the same way.
module xhci_ep_context (
  input  wire       clk,
  input  wire       rst_n,

  input  wire       cfg_valid,    // a Configure Endpoint command is executing
  input  wire       my_drop,      // ...and its Drop mask names me
  input  wire       my_add,       // ...and its Add mask names me
  input  wire       is_slot_ctx,  // this context is index 0: the SLOT

  input  wire       db_valid,     // a doorbell was rung
  input  wire       db_is_mine,   // ...for this endpoint

  input  wire       ep_error,     // a transfer errored: halt
  input  wire       reset_ep,     // a Reset Endpoint command

  output wire [2:0] state,
  output wire       usable,       // transfers may run
  output wire       reinitialised,// dropped AND added in one command
  output wire       illegal_drop, // D0: the slot context cannot be dropped
  output wire       db_ignored,   // a doorbell that correctly did nothing

  output reg [31:0] n_configured,
  output reg [31:0] n_disabled,
  output reg [31:0] n_reinit,
  output reg [31:0] n_db_ignored,
  output reg [31:0] n_illegal_drop,
  output reg [31:0] n_halts
);
  localparam [2:0] EP_DISABLED = 3'd0,  // not configured; nothing may run
                   EP_STOPPED  = 3'd1,  // configured, idle, ready to start
                   EP_RUNNING  = 3'd2,  // transfers in progress
                   EP_HALTED   = 3'd3;  // errored; needs a Reset Endpoint

  reg [2:0] st_r;

  assign state  = st_r;
  assign usable = (st_r == EP_RUNNING);

  // ---- The Configure Endpoint decode ----
  //
  // D0 would mean "drop the slot context", which a Configure Endpoint
  // command may not do. It is ignored, and reported so that a driver bug is
  // visible rather than silently destroying the device's addressing.
  assign illegal_drop = cfg_valid && is_slot_ctx && my_drop;

  wire drop_applies = cfg_valid && my_drop && !is_slot_ctx;
  wire add_applies  = cfg_valid && my_add;

  // THE ordering. Both flags set is a RE-INITIALISATION, not a
  // contradiction: tear down, then build again, atomically.
  assign reinitialised = drop_applies && add_applies;

  // A doorbell that correctly did nothing. Legal, expected, and worth
  // counting -- a high ignored-doorbell count is software ringing for
  // endpoints it has not configured.
  wire db_for_me = db_valid && db_is_mine;
  assign db_ignored = db_for_me && (st_r != EP_STOPPED);

  // ---- Next state, as ONE priority chain ----
  //
  // A command outranks a doorbell: the doorbell is a hint about a ring, and
  // the command is a statement about whether the endpoint exists at all.
  // An error outranks a doorbell for the same reason.
  reg [2:0] st_n;
  always @* begin
    st_n = st_r;

    if (cfg_valid) begin
      // Drop first, then Add -- and because Add is second, setting both
      // leaves the endpoint STOPPED rather than DISABLED.
      if (drop_applies) st_n = EP_DISABLED;
      if (add_applies)  st_n = EP_STOPPED;
    end else if (ep_error) begin
      // Only a configured endpoint can error. A disabled one is not running
      // anything that could fail.
      if (st_r != EP_DISABLED) st_n = EP_HALTED;
    end else if (reset_ep) begin
      // Reset Endpoint clears a halt and ONLY a halt. It is not a general
      // "put this endpoint back to idle" command.
      if (st_r == EP_HALTED) st_n = EP_STOPPED;
    end else if (db_for_me) begin
      // A HINT. It starts an endpoint that is ready to start, and does
      // nothing at all otherwise.
      if (st_r == EP_STOPPED) st_n = EP_RUNNING;
    end
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r           <= EP_DISABLED;
      n_configured   <= 32'd0;
      n_disabled     <= 32'd0;
      n_reinit       <= 32'd0;
      n_db_ignored   <= 32'd0;
      n_illegal_drop <= 32'd0;
      n_halts        <= 32'd0;
    end else begin
      st_r <= st_n;

      if ((st_n != EP_DISABLED) && (st_r == EP_DISABLED))
                           n_configured   <= n_configured + 32'd1;
      if ((st_n == EP_DISABLED) && (st_r != EP_DISABLED))
                           n_disabled     <= n_disabled + 32'd1;
      if (reinitialised)   n_reinit       <= n_reinit + 32'd1;
      if (db_ignored)      n_db_ignored   <= n_db_ignored + 32'd1;
      if (illegal_drop)    n_illegal_drop <= n_illegal_drop + 32'd1;
      if ((st_n == EP_HALTED) && (st_r != EP_HALTED))
                           n_halts        <= n_halts + 32'd1;
    end
  end
endmodule

Note that the drop/add ordering is written as two successive assignments inside one always block — if (drop_applies) st_n = EP_DISABLED; if (add_applies) st_n = EP_STOPPED; — which is exactly the pattern Chapter 21.1 warned against for mutually exclusive updates.

It is correct here, and the difference is worth naming: these updates are not mutually exclusive, and the later one winning is the specification. "Drop, then add" is literally what the command means, and writing it as two ordered assignments is the clearest possible statement of it. The danger in 21.1 was that two assignments looked independent while the order silently decided the answer; here the order is the answer, and it is the first thing the comment says.

7. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// xhci_ep_context -- the per-endpoint context an xHCI host controller owns,
// and the ordering rule inside a Configure Endpoint command that makes
// "drop it and add it" mean something rather than nothing.
//
// SOFTWARE DOES NOT OWN THIS STATE
//
// Every endpoint on every device has a CONTEXT: a structure in host memory
// holding its type, its packet size, its ring pointer and its current state.
// The controller reads and WRITES it -- the state field in particular is
// hardware's, updated as transfers complete, stall, or error out.
//
// So software cannot simply edit it. A context hardware owns may be written
// by hardware at any moment, and a read-modify-write from software would
// silently discard whatever hardware wrote in between.
//
// xHCI solves this by having TWO of them. The OUTPUT context is hardware's
// and software only reads it. The INPUT context is software's scratch copy,
// and software hands it over with a command:
//
//     software fills an INPUT context
//     software issues Configure Endpoint, pointing at it
//     hardware reads it, applies it, and updates the OUTPUT context
//
// No shared mutable structure, no read-modify-write race, and one clearly
// identified moment of transfer. That is the same shape as the cycle bit in
// chapter 22.2: ownership expressed structurally rather than by convention.
//
// THE INPUT CONTROL CONTEXT: ADD AND DROP FLAGS
//
// The Input Context begins with a control section holding two 32-bit masks:
//
//     Drop Context flags  D31..D0   endpoints to TEAR DOWN
//     Add  Context flags  A31..A0   endpoints to SET UP
//
// Bit 1 is endpoint 0, bit 2 the next, and so on. Bit 0 refers to the SLOT
// context rather than to an endpoint.
//
// And the rule that makes the pair useful:
//
//     DROP FLAGS ARE APPLIED BEFORE ADD FLAGS.
//
// Setting both bits for the same endpoint in one command is therefore not a
// contradiction. It is a RE-INITIALISATION: tear the endpoint down and build
// it again, atomically, in one command, with no window in which the endpoint
// does not exist. Software uses it to change an endpoint's packet size or
// ring pointer without a teardown that another thread could observe.
//
// Apply Add first and the same command means the exact opposite -- set it up,
// then tear it down -- and the endpoint ends up DISABLED. Same bits, same
// command, and the device stops working.
//
// D0 IS NOT A LEGAL DROP
//
// Bit 0 of the DROP mask would mean "drop the slot context", and a Configure
// Endpoint command may not do that -- the slot is torn down by Disable Slot,
// not by a configuration change. So D0 is ignored, and a controller that
// honours it destroys the device's addressing on a routine reconfiguration.
//
// A DOORBELL IS A HINT, NOT A COMMAND
//
// Ringing an endpoint's doorbell means "there may be new work on your ring".
// It is not an instruction to start. A doorbell for an endpoint that is
// DISABLED, or HALTED and not yet reset, is perfectly legal and does
// NOTHING -- software is allowed to be optimistic, and hardware re-checks
// rather than assuming.
//
// A controller that treats the doorbell as a command starts a halted
// endpoint without the reset that was supposed to clear the error, and the
// first thing it does is fail again in the same way.
package xhci_ctx_pkg;
  // The endpoint states an xHCI context can hold. They are an enumeration
  // rather than an encoding because THREE of them are "not transferring" and
  // they need completely different actions from software: DISABLED needs a
  // Configure Endpoint, STOPPED needs a doorbell, HALTED needs a Reset
  // Endpoint, and ringing the wrong one of those does nothing at all.
  typedef enum logic [2:0] {
    EP_DISABLED = 3'd0,   // not configured; nothing may run
    EP_STOPPED  = 3'd1,   // configured, idle, ready to start
    EP_RUNNING  = 3'd2,   // transfers in progress
    EP_HALTED   = 3'd3    // errored; needs a Reset Endpoint
  } ep_state_e;
endpackage

module xhci_ep_context
  import xhci_ctx_pkg::*;
(
  input  logic       clk,
  input  logic       rst_n,

  input  logic       cfg_valid,    // a Configure Endpoint command executing
  input  logic       my_drop,      // ...and its Drop mask names me
  input  logic       my_add,       // ...and its Add mask names me
  input  logic       is_slot_ctx,  // this context is index 0: the SLOT

  input  logic       db_valid,     // a doorbell was rung
  input  logic       db_is_mine,   // ...for this endpoint

  input  logic       ep_error,     // a transfer errored: halt
  input  logic       reset_ep,     // a Reset Endpoint command

  output ep_state_e  state,
  output logic       usable,        // transfers may run
  output logic       reinitialised, // dropped AND added in one command
  output logic       illegal_drop,  // D0: the slot cannot be dropped
  output logic       db_ignored,    // a doorbell that correctly did nothing

  output logic [31:0] n_configured,
  output logic [31:0] n_disabled,
  output logic [31:0] n_reinit,
  output logic [31:0] n_db_ignored,
  output logic [31:0] n_illegal_drop,
  output logic [31:0] n_halts
);

  ep_state_e st_r;

  assign state  = st_r;
  assign usable = (st_r == EP_RUNNING);

  // ---- The Configure Endpoint decode ----
  //
  // D0 would mean "drop the slot context", which a Configure Endpoint
  // command may not do. It is ignored, and reported so that a driver bug is
  // visible rather than silently destroying the device's addressing.
  assign illegal_drop = cfg_valid && is_slot_ctx && my_drop;

  logic drop_applies, add_applies;
  assign drop_applies = cfg_valid && my_drop && !is_slot_ctx;
  assign add_applies  = cfg_valid && my_add;

  // THE ordering. Both flags set is a RE-INITIALISATION, not a
  // contradiction: tear down, then build again, atomically.
  assign reinitialised = drop_applies && add_applies;

  // A doorbell that correctly did nothing. Legal, expected, and worth
  // counting -- a high ignored-doorbell count is software ringing for
  // endpoints it has not configured.
  logic db_for_me;
  assign db_for_me = db_valid && db_is_mine;
  assign db_ignored = db_for_me && (st_r != EP_STOPPED);

  // ---- Next state, as ONE priority chain ----
  //
  // A command outranks a doorbell: the doorbell is a hint about a ring, and
  // the command is a statement about whether the endpoint exists at all.
  // An error outranks a doorbell for the same reason.
  ep_state_e st_n;
  always_comb begin
    st_n = st_r;

    if (cfg_valid) begin
      // Drop first, then Add -- and because Add is second, setting both
      // leaves the endpoint STOPPED rather than DISABLED.
      if (drop_applies) st_n = EP_DISABLED;
      if (add_applies)  st_n = EP_STOPPED;
    end else if (ep_error) begin
      // Only a configured endpoint can error. A disabled one is not running
      // anything that could fail.
      if (st_r != EP_DISABLED) st_n = EP_HALTED;
    end else if (reset_ep) begin
      // Reset Endpoint clears a halt and ONLY a halt. It is not a general
      // "put this endpoint back to idle" command.
      if (st_r == EP_HALTED) st_n = EP_STOPPED;
    end else if (db_for_me) begin
      // A HINT. It starts an endpoint that is ready to start, and does
      // nothing at all otherwise.
      if (st_r == EP_STOPPED) st_n = EP_RUNNING;
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_r           <= EP_DISABLED;
      n_configured   <= '0;
      n_disabled     <= '0;
      n_reinit       <= '0;
      n_db_ignored   <= '0;
      n_illegal_drop <= '0;
      n_halts        <= '0;
    end else begin
      st_r <= st_n;

      if ((st_n != EP_DISABLED) && (st_r == EP_DISABLED))
                           n_configured   <= n_configured + 1;
      if ((st_n == EP_DISABLED) && (st_r != EP_DISABLED))
                           n_disabled     <= n_disabled + 1;
      if (reinitialised)   n_reinit       <= n_reinit + 1;
      if (db_ignored)      n_db_ignored   <= n_db_ignored + 1;
      if (illegal_drop)    n_illegal_drop <= n_illegal_drop + 1;
      if ((st_n == EP_HALTED) && (st_r != EP_HALTED))
                           n_halts        <= n_halts + 1;
    end
  end
endmodule

8. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- xhci_ep_context -- the per-endpoint context an xHCI host controller owns,
-- and the ordering rule inside a Configure Endpoint command that makes
-- "drop it and add it" mean something rather than nothing.
--
-- SOFTWARE DOES NOT OWN THIS STATE
--
-- Every endpoint on every device has a CONTEXT: a structure in host memory
-- holding its type, its packet size, its ring pointer and its current state.
-- The controller reads and WRITES it -- the state field in particular is
-- hardware's, updated as transfers complete, stall, or error out.
--
-- So software cannot simply edit it. A context hardware owns may be written
-- by hardware at any moment, and a read-modify-write from software would
-- silently discard whatever hardware wrote in between.
--
-- xHCI solves this by having TWO of them. The OUTPUT context is hardware's
-- and software only reads it. The INPUT context is software's scratch copy,
-- handed over with a command:
--
--     software fills an INPUT context
--     software issues Configure Endpoint, pointing at it
--     hardware reads it, applies it, and updates the OUTPUT context
--
-- No shared mutable structure, no read-modify-write race, and one clearly
-- identified moment of transfer. That is the same shape as the cycle bit in
-- chapter 22.2: ownership expressed structurally rather than by convention.
--
-- THE INPUT CONTROL CONTEXT: ADD AND DROP FLAGS
--
-- The Input Context begins with a control section holding two 32-bit masks:
--
--     Drop Context flags  D31..D0   endpoints to TEAR DOWN
--     Add  Context flags  A31..A0   endpoints to SET UP
--
-- Bit 1 is endpoint 0, bit 2 the next, and so on. Bit 0 refers to the SLOT
-- context rather than to an endpoint.
--
-- And the rule that makes the pair useful:
--
--     DROP FLAGS ARE APPLIED BEFORE ADD FLAGS.
--
-- Setting both bits for the same endpoint in one command is therefore not a
-- contradiction. It is a RE-INITIALISATION: tear the endpoint down and build
-- it again, atomically, in one command, with no window in which the endpoint
-- does not exist. Software uses it to change an endpoint's packet size or
-- ring pointer without a teardown another thread could observe.
--
-- Apply Add first and the same command means the exact opposite -- set it up,
-- then tear it down -- and the endpoint ends up DISABLED.
--
-- D0 IS NOT A LEGAL DROP
--
-- Bit 0 of the DROP mask would mean "drop the slot context", and a Configure
-- Endpoint command may not do that -- the slot is torn down by Disable Slot,
-- not by a configuration change. So D0 is ignored, and a controller that
-- honours it destroys the device's addressing on a routine reconfiguration.
--
-- A DOORBELL IS A HINT, NOT A COMMAND
--
-- Ringing an endpoint's doorbell means "there may be new work on your ring".
-- It is not an instruction to start. A doorbell for an endpoint that is
-- DISABLED, or HALTED and not yet reset, is perfectly legal and does
-- NOTHING -- software is allowed to be optimistic, and hardware re-checks
-- rather than assuming.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package xhci_ctx_pkg is
  -- The endpoint states an xHCI context can hold. An enumeration rather than
  -- an encoding because THREE of them are "not transferring" and they need
  -- completely different actions from software: EP_DISABLED needs a
  -- Configure Endpoint, EP_STOPPED needs a doorbell, EP_HALTED needs a Reset
  -- Endpoint, and doing the wrong one of those does nothing at all.
  type ep_state_t is (
    EP_DISABLED,   -- not configured; nothing may run
    EP_STOPPED,    -- configured, idle, ready to start
    EP_RUNNING,    -- transfers in progress
    EP_HALTED      -- errored; needs a Reset Endpoint
  );

  function st_code(s : ep_state_t) return std_logic_vector;
end package;

package body xhci_ctx_pkg is
  function st_code(s : ep_state_t) return std_logic_vector is
  begin
    return std_logic_vector(to_unsigned(ep_state_t'pos(s), 3));
  end function;
end package body;

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

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

    cfg_valid      : in  std_logic;  -- a Configure Endpoint is executing
    my_drop        : in  std_logic;  -- ...and its Drop mask names me
    my_add         : in  std_logic;  -- ...and its Add mask names me
    is_slot_ctx    : in  std_logic;  -- this context is index 0: the SLOT

    db_valid       : in  std_logic;  -- a doorbell was rung
    db_is_mine     : in  std_logic;  -- ...for this endpoint

    ep_error       : in  std_logic;  -- a transfer errored: halt
    reset_ep       : in  std_logic;  -- a Reset Endpoint command

    state          : out std_logic_vector(2 downto 0);
    usable         : out std_logic;  -- transfers may run
    reinitialised  : out std_logic;  -- dropped AND added in one command
    illegal_drop   : out std_logic;  -- D0: the slot cannot be dropped
    db_ignored     : out std_logic;  -- a doorbell that correctly did nothing

    n_configured   : out std_logic_vector(31 downto 0);
    n_disabled     : out std_logic_vector(31 downto 0);
    n_reinit       : out std_logic_vector(31 downto 0);
    n_db_ignored   : out std_logic_vector(31 downto 0);
    n_illegal_drop : out std_logic_vector(31 downto 0);
    n_halts        : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of xhci_ep_context is
  signal st_r, st_n : ep_state_t := EP_DISABLED;

  signal drop_applies, add_applies, db_for_me : std_logic;
  signal reinit_s, illegal_s, dbig_s : std_logic;

  signal cfg_c, dis_c, re_c, dbi_c, ill_c, hal_c : unsigned(31 downto 0)
       := (others => '0');
begin
  state  <= st_code(st_r);
  usable <= '1' when st_r = EP_RUNNING else '0';

  -- ---- The Configure Endpoint decode ----
  --
  -- D0 would mean "drop the slot context", which a Configure Endpoint
  -- command may not do. It is ignored, and reported so that a driver bug is
  -- visible rather than silently destroying the device's addressing.
  illegal_s <= '1' when (cfg_valid = '1' and is_slot_ctx = '1'
                         and my_drop = '1') else '0';

  drop_applies <= '1' when (cfg_valid = '1' and my_drop = '1'
                            and is_slot_ctx = '0') else '0';
  add_applies  <= cfg_valid and my_add;

  -- THE ordering. Both flags set is a RE-INITIALISATION, not a
  -- contradiction: tear down, then build again, atomically.
  reinit_s <= drop_applies and add_applies;

  -- A doorbell that correctly did nothing. Legal, expected, and worth
  -- counting -- a high ignored-doorbell count is software ringing for
  -- endpoints it has not configured.
  db_for_me <= db_valid and db_is_mine;
  dbig_s    <= '1' when (db_for_me = '1' and st_r /= EP_STOPPED) else '0';

  reinitialised <= reinit_s;
  illegal_drop  <= illegal_s;
  db_ignored    <= dbig_s;

  -- ---- Next state, as ONE priority chain ----
  --
  -- A command outranks a doorbell: the doorbell is a hint about a ring, and
  -- the command is a statement about whether the endpoint exists at all.
  -- An error outranks a doorbell for the same reason.
  nextstate : process (st_r, cfg_valid, drop_applies, add_applies,
                       ep_error, reset_ep, db_for_me)
  begin
    st_n <= st_r;

    if cfg_valid = '1' then
      -- Drop first, then Add -- and because Add is second, setting both
      -- leaves the endpoint STOPPED rather than DISABLED.
      if drop_applies = '1' then
        st_n <= EP_DISABLED;
      end if;
      if add_applies = '1' then
        st_n <= EP_STOPPED;
      end if;
    elsif ep_error = '1' then
      -- Only a configured endpoint can error. A disabled one is not running
      -- anything that could fail.
      if st_r /= EP_DISABLED then
        st_n <= EP_HALTED;
      end if;
    elsif reset_ep = '1' then
      -- Reset Endpoint clears a halt and ONLY a halt. It is not a general
      -- "put this endpoint back to idle" command.
      if st_r = EP_HALTED then
        st_n <= EP_STOPPED;
      end if;
    elsif db_for_me = '1' then
      -- A HINT. It starts an endpoint that is ready to start, and does
      -- nothing at all otherwise.
      if st_r = EP_STOPPED then
        st_n <= EP_RUNNING;
      end if;
    end if;
  end process;

  regs : process (clk, rst_n)
  begin
    if rst_n = '0' then
      st_r  <= EP_DISABLED;
      cfg_c <= (others => '0');
      dis_c <= (others => '0');
      re_c  <= (others => '0');
      dbi_c <= (others => '0');
      ill_c <= (others => '0');
      hal_c <= (others => '0');
    elsif rising_edge(clk) then
      st_r <= st_n;

      if st_n /= EP_DISABLED and st_r = EP_DISABLED then
        cfg_c <= cfg_c + 1;
      end if;
      if st_n = EP_DISABLED and st_r /= EP_DISABLED then
        dis_c <= dis_c + 1;
      end if;
      if reinit_s = '1' then
        re_c <= re_c + 1;
      end if;
      if dbig_s = '1' then
        dbi_c <= dbi_c + 1;
      end if;
      if illegal_s = '1' then
        ill_c <= ill_c + 1;
      end if;
      if st_n = EP_HALTED and st_r /= EP_HALTED then
        hal_c <= hal_c + 1;
      end if;
    end if;
  end process;

  n_configured   <= std_logic_vector(cfg_c);
  n_disabled     <= std_logic_vector(dis_c);
  n_reinit       <= std_logic_vector(re_c);
  n_db_ignored   <= std_logic_vector(dbi_c);
  n_illegal_drop <= std_logic_vector(ill_c);
  n_halts        <= std_logic_vector(hal_c);
end architecture;

9. Seeing the Re-initialisation

Configure, start, error, an ignored doorbell, reset — and an atomic re-initialisation

xhci_ep_context — the life of an endpoint, and drop+add

10 cycles
A ten-cycle waveform. A Configure Endpoint with the Add flag moves the endpoint from DISABLED to STOPPED. A doorbell starts it. A transfer error halts it. A doorbell while halted raises db_ignored and changes nothing. A Reset Endpoint returns it to STOPPED. A final command with both Drop and Add set raises reinitialised and leaves the endpoint STOPPED.error: HALTEDerror: HALTEDdoorbell ignored: it is a hintdoorbell ignored: it is ahintdrop + add: re-initialiseddrop + add: re-initialisedclkcfg_validmy_dropmy_adddb_validep_errorreset_epstateDISABLEDDISABLEDSTOPPEDRUNNINGHALTEDHALTEDSTOPPEDSTOPPEDRUNNINGSTOPPEDdb_ignoredt0t1t2t3t4t5t6t7t8t9
Cycle 5 is the doorbell that correctly does nothing: the endpoint is HALTED and a doorbell is a hint. Cycle 9 sets Drop and Add together, and because drops go first the endpoint ends up STOPPED — configured and ready — rather than DISABLED.

10. The Testbenches

Each suite sweeps every state against every combination of the eight control inputs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   4 endpoint states
     x  cfg_valid  x  my_drop  x  my_add  x  is_slot_ctx
     x  db_valid   x  db_is_mine  x  ep_error  x  reset_ep

     =  4 x 256  =  1024 one-step transitions

Every starting state is reached through legal transitions only — a Configure Endpoint to leave DISABLED, a doorbell to start, an error to halt — and the reference model states the drop/add outcome as four explicit cases where the design applies two successive assignments, so the model cannot share an ordering bug with the design.

10.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_ec_v;
  reg clk=0, rst_n=0;
  reg cfg_valid=0, my_drop=0, my_add=0, is_slot_ctx=0;
  reg db_valid=0, db_is_mine=0, ep_error=0, reset_ep=0;
  wire [2:0] state;
  wire usable, reinitialised, illegal_drop, db_ignored;
  wire [31:0] n_configured, n_disabled, n_reinit, n_db_ignored;
  wire [31:0] n_illegal_drop, n_halts;
  always #5 clk=~clk;

  xhci_ep_context dut (
    .clk(clk), .rst_n(rst_n), .cfg_valid(cfg_valid), .my_drop(my_drop),
    .my_add(my_add), .is_slot_ctx(is_slot_ctx), .db_valid(db_valid),
    .db_is_mine(db_is_mine), .ep_error(ep_error), .reset_ep(reset_ep),
    .state(state), .usable(usable), .reinitialised(reinitialised),
    .illegal_drop(illegal_drop), .db_ignored(db_ignored),
    .n_configured(n_configured), .n_disabled(n_disabled),
    .n_reinit(n_reinit), .n_db_ignored(n_db_ignored),
    .n_illegal_drop(n_illegal_drop), .n_halts(n_halts));

  localparam [2:0] EP_DISABLED=0, EP_STOPPED=1, EP_RUNNING=2, EP_HALTED=3;

  // ---- SHADOW MODEL of the endpoint state ----
  integer s_st;
  integer m_cfg, m_dis, m_re, m_dbi, m_ill, m_hal;

  integer errors=0, i, st0, a, b, c, d, e, f, g;
  integer n_exh=0;
  integer n_vis [0:3];
  integer n_reinit_seen=0, n_dbig=0, n_ill=0, n_halt=0;

  task check(input cond, input [639:0] msg);
    begin if (!cond) begin errors=errors+1;
      if (errors <= 25)
        $display("  FAIL: %0s (cfg=%b drop=%b add=%b slot=%b db=%b mine=%b err=%b rst=%b | state=%0d usable=%b reinit=%b illegal=%b dbig=%b || model=%0d, t=%0t)",
                 msg, cfg_valid, my_drop, my_add, is_slot_ctx, db_valid,
                 db_is_mine, ep_error, reset_ep, state, usable,
                 reinitialised, illegal_drop, db_ignored, s_st, $time);
    end end
  endtask

  // The model is a flat case over the CURRENT state where the design applies
  // drop and add as two successive assignments -- a different route, and one
  // that cannot share an ordering bug with it.
  task model(output integer e_st, output e_reinit, output e_ill,
             output e_dbig);
    reg dropa, adda, dbme;
    begin
      e_ill  = cfg_valid && is_slot_ctx && my_drop;
      dropa  = cfg_valid && my_drop && !is_slot_ctx;
      adda   = cfg_valid && my_add;
      dbme   = db_valid && db_is_mine;
      e_reinit = dropa && adda;
      e_dbig   = dbme && (s_st != EP_STOPPED);

      e_st = s_st;
      if (cfg_valid) begin
        // stated as the four cases rather than as two assignments
        if      (dropa && adda)   e_st = EP_STOPPED;   // re-initialised
        else if (dropa)           e_st = EP_DISABLED;
        else if (adda)            e_st = EP_STOPPED;
      end else if (ep_error) begin
        case (s_st)
          EP_DISABLED: e_st = EP_DISABLED;
          default:     e_st = EP_HALTED;
        endcase
      end else if (reset_ep) begin
        if (s_st == EP_HALTED) e_st = EP_STOPPED;
      end else if (dbme) begin
        if (s_st == EP_STOPPED) e_st = EP_RUNNING;
      end
    end
  endtask

  task check_comb;
    integer e_st;
    reg e_reinit, e_ill, e_dbig;
    begin
      model(e_st, e_reinit, e_ill, e_dbig);

      check(state === s_st[2:0],       "state matches the shadow model");
      check(usable === (s_st == EP_RUNNING), "usable matches the state");
      check(reinitialised === e_reinit, "reinitialised matches the model");
      check(illegal_drop === e_ill,     "illegal_drop matches the model");
      check(db_ignored === e_dbig,      "db_ignored matches the model");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE property. Drop before Add: an endpoint named by BOTH masks
      //    is re-initialised, not torn down.
      if (cfg_valid && my_drop && my_add && !is_slot_ctx)
        check(reinitialised,
              "drop+add in one command was not reported as a re-initialisation");
      // 2. A doorbell is a HINT. It never starts an endpoint that is not
      //    ready to start.
      if (db_valid && db_is_mine && !cfg_valid && !ep_error && !reset_ep) begin
        if (state === EP_DISABLED)
          check(db_ignored,
                "a doorbell on a DISABLED endpoint was not ignored");
        if (state === EP_HALTED)
          check(db_ignored,
                "a doorbell on a HALTED endpoint was not ignored -- the reset that clears the error has not happened");
      end
      // 3. D0 is not a legal drop: the slot context cannot be dropped by a
      //    Configure Endpoint command.
      if (cfg_valid && is_slot_ctx && my_drop)
        check(illegal_drop,
              "a drop of the SLOT context was not flagged as illegal");
      // 4. Only a RUNNING endpoint is usable.
      check(usable === (state === EP_RUNNING),
            "usable disagrees with the state");
      // 5. A disabled endpoint runs nothing.
      if (state === EP_DISABLED)
        check(!usable, "a DISABLED endpoint reported itself usable");
      // 6. A halted endpoint runs nothing either.
      if (state === EP_HALTED)
        check(!usable, "a HALTED endpoint reported itself usable");
      // 7. The state is always one of the four that exist.
      check(state <= EP_HALTED, "the endpoint reached an undefined state");
      // 8. A re-initialisation is always also a drop and an add.
      if (reinitialised)
        check(cfg_valid && my_drop && my_add && !is_slot_ctx,
              "a re-initialisation was reported with no drop+add pair");

      if (s_st <= 3) n_vis[s_st] = n_vis[s_st] + 1;
      if (e_reinit) n_reinit_seen = n_reinit_seen + 1;
      if (e_dbig)   n_dbig = n_dbig + 1;
      if (e_ill)    n_ill  = n_ill + 1;
      if ((e_st == EP_HALTED) && (s_st != EP_HALTED)) n_halt = n_halt + 1;
    end
  endtask

  task model_step;
    integer e_st;
    reg e_reinit, e_ill, e_dbig;
    begin
      model(e_st, e_reinit, e_ill, e_dbig);
      if ((e_st != EP_DISABLED) && (s_st == EP_DISABLED)) m_cfg = m_cfg + 1;
      if ((e_st == EP_DISABLED) && (s_st != EP_DISABLED)) m_dis = m_dis + 1;
      if (e_reinit) m_re  = m_re + 1;
      if (e_dbig)   m_dbi = m_dbi + 1;
      if (e_ill)    m_ill = m_ill + 1;
      if ((e_st == EP_HALTED) && (s_st != EP_HALTED)) m_hal = m_hal + 1;
      s_st = e_st;
    end
  endtask

  task step;
    begin
      #1;
      check_comb;
      model_step;
      @(posedge clk); #1;
      check(state === s_st[2:0], "state tracked the model");
      check(n_configured   === m_cfg[31:0], "n_configured matches the model");
      check(n_disabled     === m_dis[31:0], "n_disabled matches the model");
      check(n_reinit       === m_re[31:0],  "n_reinit matches the model");
      check(n_db_ignored   === m_dbi[31:0], "n_db_ignored matches the model");
      check(n_illegal_drop === m_ill[31:0], "n_illegal_drop matches the model");
      check(n_halts        === m_hal[31:0], "n_halts matches the model");
    end
  endtask

  task idle_in;
    begin
      cfg_valid=0; my_drop=0; my_add=0; db_valid=0; db_is_mine=0;
      ep_error=0; reset_ep=0;
    end
  endtask

  task hard_reset;
    begin
      rst_n=0; idle_in; is_slot_ctx=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      s_st=EP_DISABLED;
      m_cfg=0; m_dis=0; m_re=0; m_dbi=0; m_ill=0; m_hal=0;
    end
  endtask

  // Walk to a chosen state using only legal transitions: Configure Endpoint
  // to leave DISABLED, a doorbell to start, an error to halt.
  task goto_state(input integer want);
    begin
      hard_reset;
      is_slot_ctx=0;
      if (want != EP_DISABLED) begin
        cfg_valid=1; my_add=1; step; idle_in;              // -> STOPPED
        if (want == EP_RUNNING) begin
          db_valid=1; db_is_mine=1; step; idle_in;         // -> RUNNING
        end else if (want == EP_HALTED) begin
          ep_error=1; step; idle_in;                       // -> HALTED
        end
      end
      #1;
      check(state === want[2:0], "goto_state reached the requested state");
    end
  endtask

  initial begin
    for (i=0;i<4;i=i+1) n_vis[i]=0;
    hard_reset;
    check(state === EP_DISABLED, "reset leaves the endpoint DISABLED");
    check(!usable, "and unusable");

    // ===== A. EXHAUSTIVE sweep =====
    // 4 endpoint states x cfg_valid x my_drop x my_add x is_slot_ctx
    //   x db_valid x db_is_mine x ep_error x reset_ep
    // = 4 x 256 = 1024 one-step transitions: every state against every
    // combination of the eight control inputs.
    for (st0=0; st0<4; st0=st0+1)
     for (a=0;a<2;a=a+1)            // cfg_valid
      for (b=0;b<2;b=b+1)           // my_drop
       for (c=0;c<2;c=c+1)          // my_add
        for (d=0;d<2;d=d+1)         // is_slot_ctx
         for (e=0;e<2;e=e+1)        // db_valid
          for (f=0;f<2;f=f+1)       // db_is_mine
           for (g=0;g<2;g=g+1)      // ep_error
            for (i=0;i<2;i=i+1) begin  // reset_ep
              goto_state(st0);
              cfg_valid=a[0]; my_drop=b[0]; my_add=c[0]; is_slot_ctx=d[0];
              db_valid=e[0]; db_is_mine=f[0]; ep_error=g[0]; reset_ep=i[0];
              step;
              n_exh = n_exh + 1;
              idle_in;
            end
    $display("  exhaustive endpoint-context sweep: %0d of %0d transitions verified",
             n_exh, 4*256);

    // ===== B. directed: the life of an endpoint =====
    hard_reset;

    // 1. Configure Endpoint with only the Add flag: the endpoint exists.
    cfg_valid=1; my_add=1; step; idle_in;
    check(state === EP_STOPPED, "an Add flag configures the endpoint");
    check(!usable, "but it is not running yet -- nothing has rung its doorbell");
    check(n_configured === 32'd1, "one configuration");

    // 2. A doorbell starts it.
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "a doorbell starts a STOPPED endpoint");
    check(usable, "and now transfers may run");
    check(n_db_ignored === 32'd0, "that doorbell was not ignored");

    // 3. A doorbell for someone ELSE does nothing at all.
    db_valid=1; db_is_mine=0; step; idle_in;
    check(state === EP_RUNNING, "a doorbell for another endpoint changes nothing");
    check(n_db_ignored === 32'd0, "and is not even counted as ignored -- it was never ours");

    // 4. A transfer errors: the endpoint halts.
    ep_error=1; step; idle_in;
    check(state === EP_HALTED, "an error halts the endpoint");
    check(!usable, "which stops transfers");
    check(n_halts === 32'd1, "one halt");

    // 5. THE doorbell rule. Software rings it anyway -- which is legal, and
    //    must do nothing, because the error has not been cleared.
    db_valid=1; db_is_mine=1; #1;
    check(db_ignored,
          "a doorbell on a HALTED endpoint is IGNORED -- it is a hint, not a command");
    step; idle_in;
    check(state === EP_HALTED, "the endpoint is still halted");
    check(n_db_ignored === 32'd1, "and the ignored doorbell was counted");

    // 6. Reset Endpoint clears the halt.
    reset_ep=1; step; idle_in;
    check(state === EP_STOPPED, "Reset Endpoint clears the halt");
    check(!usable, "leaving it stopped, not running");

    // 7. Reset Endpoint from a state that is NOT halted does nothing. It is
    //    not a general "go back to idle" command.
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "started again");
    reset_ep=1; step; idle_in;
    check(state === EP_RUNNING,
          "Reset Endpoint on a RUNNING endpoint does nothing");

    // ===== C. directed: drop, add, and the order between them =====
    hard_reset;
    cfg_valid=1; my_add=1; step; idle_in;
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "a configured, running endpoint");

    // 8. Drop alone tears it down.
    cfg_valid=1; my_drop=1; step; idle_in;
    check(state === EP_DISABLED, "a Drop flag tears the endpoint down");
    check(n_disabled === 32'd1, "one teardown");

    // 9. THE case. Drop AND Add in the same command is a RE-INITIALISATION,
    //    because drops are applied first. The endpoint ends up configured.
    cfg_valid=1; my_add=1; step; idle_in;
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "running again");
    cfg_valid=1; my_drop=1; my_add=1; #1;
    check(reinitialised, "drop and add together is a re-initialisation");
    step; idle_in;
    check(state === EP_STOPPED,
          "so the endpoint ends up CONFIGURED, not disabled -- drops go first");
    check(n_reinit === 32'd1, "one re-initialisation");
    check(!usable, "freshly re-initialised, so stopped rather than running");

    // 10. D0 is not a legal drop. The slot context cannot be dropped by a
    //     Configure Endpoint command.
    hard_reset;
    is_slot_ctx=1;
    cfg_valid=1; my_add=1; step; idle_in;
    check(state === EP_STOPPED, "the slot context can be ADDED");
    cfg_valid=1; my_drop=1; #1;
    check(illegal_drop, "but a DROP of the slot context is illegal");
    step; idle_in;
    check(state === EP_STOPPED,
          "and is ignored -- the slot survives a routine reconfiguration");
    check(n_illegal_drop === 32'd1, "and the driver bug was counted");

    // 11. A disabled endpoint cannot error -- it is not running anything.
    hard_reset;
    is_slot_ctx=0;
    ep_error=1; step; idle_in;
    check(state === EP_DISABLED, "a DISABLED endpoint does not halt on error");
    check(n_halts === 32'd0, "and no halt was counted");

    // 12. A command outranks a doorbell in the same cycle.
    hard_reset;
    cfg_valid=1; my_add=1; step; idle_in;
    cfg_valid=1; my_drop=1; db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_DISABLED,
          "a Configure Endpoint command outranks a doorbell in the same cycle");

    // ===== D. randomised =====
    hard_reset;
    for (i=0;i<40000;i=i+1) begin
      cfg_valid   = ({$random}%4)==0;
      my_drop     = ({$random}%2);
      my_add      = ({$random}%2);
      is_slot_ctx = ({$random}%8)==0;
      db_valid    = ({$random}%2);
      db_is_mine  = ({$random}%2);
      ep_error    = ({$random}%12)==0;
      reset_ep    = ({$random}%10)==0;
      step;
    end

    for (i=0;i<4;i=i+1)
      check(n_vis[i] > 500, "every endpoint state was visited many times");
    check(n_reinit_seen > 500, "re-initialisations happened often");
    check(n_dbig        > 500, "doorbells were correctly ignored often");
    check(n_ill         > 200, "illegal slot drops were presented often");
    check(n_halt        > 200, "halts happened often");

    $display("");
    $display("  REACH: transitions=%0d | states: disabled=%0d stopped=%0d running=%0d halted=%0d",
             n_exh, n_vis[0], n_vis[1], n_vis[2], n_vis[3]);
    $display("  CASES: re-initialisations=%0d ignored-doorbells=%0d illegal-slot-drops=%0d halts=%0d",
             n_reinit_seen, n_dbig, n_ill, n_halt);
    $display("  COUNTERS: configured=%0d disabled=%0d reinit=%0d db-ignored=%0d illegal=%0d halts=%0d",
             n_configured, n_disabled, n_reinit, n_db_ignored,
             n_illegal_drop, n_halts);
    $display("  [Verilog] xhci_ep_context: %0d errors", errors);
    $display("  [Verilog] %0s", errors==0 ? "PASS" : "FAIL");
    $display("");
    $finish;
  end
endmodule

10.2 SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_ec_sv;
  import xhci_ctx_pkg::*;

  logic clk=0, rst_n=0;
  logic cfg_valid=0, my_drop=0, my_add=0, is_slot_ctx=0;
  logic db_valid=0, db_is_mine=0, ep_error=0, reset_ep=0;
  ep_state_e state;
  logic usable, reinitialised, illegal_drop, db_ignored;

  // Icarus seeds $random and $urandom identically, so an unseeded run would
  // replay the Verilog suite's stimulus exactly. See chapter 20.5 section 9.2.
  int urandom_seed = 22404;
  wire [31:0] n_configured, n_disabled, n_reinit, n_db_ignored;
  wire [31:0] n_illegal_drop, n_halts;
  always #5 clk=~clk;

  xhci_ep_context dut (
    .clk, .rst_n, .cfg_valid, .my_drop, .my_add, .is_slot_ctx, .db_valid,
    .db_is_mine, .ep_error, .reset_ep, .state, .usable, .reinitialised,
    .illegal_drop, .db_ignored, .n_configured, .n_disabled, .n_reinit,
    .n_db_ignored, .n_illegal_drop, .n_halts);

  // ---- SHADOW MODEL of the endpoint state ----
  int s_st;
  int m_cfg, m_dis, m_re, m_dbi, m_ill, m_hal;

  int errors=0, i, st0, a, b, c, d, e, f, g;
  int n_exh=0;
  int n_vis [4];
  int n_reinit_seen=0, n_dbig=0, n_ill=0, n_halt=0;

  task automatic check(input bit cond, input string msg);
    // Icarus will not call .name() on a net, so the enum output is copied
    // into a variable of the same type before being printed.
    ep_state_e st_v;
    if (!cond) begin
      errors++;
      st_v = state;
      if (errors <= 25)
        $display("  FAIL: %0s (cfg=%b drop=%b add=%b slot=%b db=%b mine=%b err=%b rst=%b | state=%s usable=%b reinit=%b illegal=%b dbig=%b || model=%0d, t=%0t)",
                 msg, cfg_valid, my_drop, my_add, is_slot_ctx, db_valid,
                 db_is_mine, ep_error, reset_ep, st_v.name(), usable,
                 reinitialised, illegal_drop, db_ignored, s_st, $time);
    end
  endtask

  // The model is a flat case over the CURRENT state where the design applies
  // drop and add as two successive assignments -- a different route, and one
  // that cannot share an ordering bug with it.
  task automatic model(output int e_st, output bit e_reinit,
                       output bit e_ill, output bit e_dbig);
    bit dropa, adda, dbme;
    begin
      e_ill  = cfg_valid && is_slot_ctx && my_drop;
      dropa  = cfg_valid && my_drop && !is_slot_ctx;
      adda   = cfg_valid && my_add;
      dbme   = db_valid && db_is_mine;
      e_reinit = dropa && adda;
      e_dbig   = dbme && (s_st != EP_STOPPED);

      e_st = s_st;
      if (cfg_valid) begin
        // stated as the four cases rather than as two assignments
        if      (dropa && adda)   e_st = EP_STOPPED;   // re-initialised
        else if (dropa)           e_st = EP_DISABLED;
        else if (adda)            e_st = EP_STOPPED;
      end else if (ep_error) begin
        case (s_st)
          EP_DISABLED: e_st = EP_DISABLED;
          default:     e_st = EP_HALTED;
        endcase
      end else if (reset_ep) begin
        if (s_st == EP_HALTED) e_st = EP_STOPPED;
      end else if (dbme) begin
        if (s_st == EP_STOPPED) e_st = EP_RUNNING;
      end
    end
  endtask

  task automatic check_comb;
    int e_st;
    bit e_reinit, e_ill, e_dbig;
    begin
      model(e_st, e_reinit, e_ill, e_dbig);

      check(state === ep_state_e'(s_st),       "state matches the shadow model");
      check(usable === (s_st == EP_RUNNING), "usable matches the state");
      check(reinitialised === e_reinit, "reinitialised matches the model");
      check(illegal_drop === e_ill,     "illegal_drop matches the model");
      check(db_ignored === e_dbig,      "db_ignored matches the model");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE property. Drop before Add: an endpoint named by BOTH masks
      //    is re-initialised, not torn down.
      if (cfg_valid && my_drop && my_add && !is_slot_ctx)
        check(reinitialised,
              "drop+add in one command was not reported as a re-initialisation");
      // 2. A doorbell is a HINT. It never starts an endpoint that is not
      //    ready to start.
      if (db_valid && db_is_mine && !cfg_valid && !ep_error && !reset_ep) begin
        if (state === EP_DISABLED)
          check(db_ignored,
                "a doorbell on a DISABLED endpoint was not ignored");
        if (state === EP_HALTED)
          check(db_ignored,
                "a doorbell on a HALTED endpoint was not ignored -- the reset that clears the error has not happened");
      end
      // 3. D0 is not a legal drop: the slot context cannot be dropped by a
      //    Configure Endpoint command.
      if (cfg_valid && is_slot_ctx && my_drop)
        check(illegal_drop,
              "a drop of the SLOT context was not flagged as illegal");
      // 4. Only a RUNNING endpoint is usable.
      check(usable === (state === EP_RUNNING),
            "usable disagrees with the state");
      // 5. A disabled endpoint runs nothing.
      if (state === EP_DISABLED)
        check(!usable, "a DISABLED endpoint reported itself usable");
      // 6. A halted endpoint runs nothing either.
      if (state === EP_HALTED)
        check(!usable, "a HALTED endpoint reported itself usable");
      // 7. The state is always one of the four that exist.
      check(state <= EP_HALTED, "the endpoint reached an undefined state");
      // 8. A re-initialisation is always also a drop and an add.
      if (reinitialised)
        check(cfg_valid && my_drop && my_add && !is_slot_ctx,
              "a re-initialisation was reported with no drop+add pair");

      n_vis[s_st] = n_vis[s_st] + 1;
      if (e_reinit) n_reinit_seen = n_reinit_seen + 1;
      if (e_dbig)   n_dbig = n_dbig + 1;
      if (e_ill)    n_ill  = n_ill + 1;
      if ((e_st == EP_HALTED) && (s_st != EP_HALTED)) n_halt = n_halt + 1;
    end
  endtask

  task automatic model_step;
    int e_st;
    bit e_reinit, e_ill, e_dbig;
    begin
      model(e_st, e_reinit, e_ill, e_dbig);
      if ((e_st != EP_DISABLED) && (s_st == EP_DISABLED)) m_cfg = m_cfg + 1;
      if ((e_st == EP_DISABLED) && (s_st != EP_DISABLED)) m_dis = m_dis + 1;
      if (e_reinit) m_re  = m_re + 1;
      if (e_dbig)   m_dbi = m_dbi + 1;
      if (e_ill)    m_ill = m_ill + 1;
      if ((e_st == EP_HALTED) && (s_st != EP_HALTED)) m_hal = m_hal + 1;
      s_st = e_st;
    end
  endtask

  task automatic step;
    begin
      #1;
      check_comb;
      model_step;
      @(posedge clk); #1;
      check(state === ep_state_e'(s_st), "state tracked the model");
      check(n_configured   === 32'(m_cfg), "n_configured matches the model");
      check(n_disabled     === 32'(m_dis), "n_disabled matches the model");
      check(n_reinit       === 32'(m_re),  "n_reinit matches the model");
      check(n_db_ignored   === 32'(m_dbi), "n_db_ignored matches the model");
      check(n_illegal_drop === 32'(m_ill), "n_illegal_drop matches the model");
      check(n_halts        === 32'(m_hal), "n_halts matches the model");
    end
  endtask

  task automatic idle_in;
    begin
      cfg_valid=0; my_drop=0; my_add=0; db_valid=0; db_is_mine=0;
      ep_error=0; reset_ep=0;
    end
  endtask

  task automatic hard_reset;
    begin
      rst_n=0; idle_in; is_slot_ctx=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      s_st=EP_DISABLED;
      m_cfg=0; m_dis=0; m_re=0; m_dbi=0; m_ill=0; m_hal=0;
    end
  endtask

  // Walk to a chosen state using only legal transitions: Configure Endpoint
  // to leave DISABLED, a doorbell to start, an error to halt.
  task automatic goto_state(input int want);
    begin
      hard_reset;
      is_slot_ctx=0;
      if (want != EP_DISABLED) begin
        cfg_valid=1; my_add=1; step; idle_in;              // -> STOPPED
        if (want == EP_RUNNING) begin
          db_valid=1; db_is_mine=1; step; idle_in;         // -> RUNNING
        end else if (want == EP_HALTED) begin
          ep_error=1; step; idle_in;                       // -> HALTED
        end
      end
      #1;
      check(state === ep_state_e'(want), "goto_state reached the requested state");
    end
  endtask

  initial begin
    void'($urandom(urandom_seed));
    foreach (n_vis[i]) n_vis[i]=0;
    hard_reset;
    check(state === EP_DISABLED, "reset leaves the endpoint DISABLED");
    check(!usable, "and unusable");

    // ===== A. EXHAUSTIVE sweep =====
    // 4 endpoint states x cfg_valid x my_drop x my_add x is_slot_ctx
    //   x db_valid x db_is_mine x ep_error x reset_ep
    // = 4 x 256 = 1024 one-step transitions: every state against every
    // combination of the eight control inputs.
    for (st0=0; st0<4; st0=st0+1)
     for (a=0;a<2;a=a+1)            // cfg_valid
      for (b=0;b<2;b=b+1)           // my_drop
       for (c=0;c<2;c=c+1)          // my_add
        for (d=0;d<2;d=d+1)         // is_slot_ctx
         for (e=0;e<2;e=e+1)        // db_valid
          for (f=0;f<2;f=f+1)       // db_is_mine
           for (g=0;g<2;g=g+1)      // ep_error
            for (i=0;i<2;i=i+1) begin  // reset_ep
              goto_state(st0);
              cfg_valid=a[0]; my_drop=b[0]; my_add=c[0]; is_slot_ctx=d[0];
              db_valid=e[0]; db_is_mine=f[0]; ep_error=g[0]; reset_ep=i[0];
              step;
              n_exh = n_exh + 1;
              idle_in;
            end
    $display("  exhaustive endpoint-context sweep: %0d of %0d transitions verified",
             n_exh, 4*256);

    // ===== B. directed: the life of an endpoint =====
    hard_reset;

    // 1. Configure Endpoint with only the Add flag: the endpoint exists.
    cfg_valid=1; my_add=1; step; idle_in;
    check(state === EP_STOPPED, "an Add flag configures the endpoint");
    check(!usable, "but it is not running yet -- nothing has rung its doorbell");
    check(n_configured === 32'd1, "one configuration");

    // 2. A doorbell starts it.
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "a doorbell starts a STOPPED endpoint");
    check(usable, "and now transfers may run");
    check(n_db_ignored === 32'd0, "that doorbell was not ignored");

    // 3. A doorbell for someone ELSE does nothing at all.
    db_valid=1; db_is_mine=0; step; idle_in;
    check(state === EP_RUNNING, "a doorbell for another endpoint changes nothing");
    check(n_db_ignored === 32'd0, "and is not even counted as ignored -- it was never ours");

    // 4. A transfer errors: the endpoint halts.
    ep_error=1; step; idle_in;
    check(state === EP_HALTED, "an error halts the endpoint");
    check(!usable, "which stops transfers");
    check(n_halts === 32'd1, "one halt");

    // 5. THE doorbell rule. Software rings it anyway -- which is legal, and
    //    must do nothing, because the error has not been cleared.
    db_valid=1; db_is_mine=1; #1;
    check(db_ignored,
          "a doorbell on a HALTED endpoint is IGNORED -- it is a hint, not a command");
    step; idle_in;
    check(state === EP_HALTED, "the endpoint is still halted");
    check(n_db_ignored === 32'd1, "and the ignored doorbell was counted");

    // 6. Reset Endpoint clears the halt.
    reset_ep=1; step; idle_in;
    check(state === EP_STOPPED, "Reset Endpoint clears the halt");
    check(!usable, "leaving it stopped, not running");

    // 7. Reset Endpoint from a state that is NOT halted does nothing. It is
    //    not a general "go back to idle" command.
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "started again");
    reset_ep=1; step; idle_in;
    check(state === EP_RUNNING,
          "Reset Endpoint on a RUNNING endpoint does nothing");

    // ===== C. directed: drop, add, and the order between them =====
    hard_reset;
    cfg_valid=1; my_add=1; step; idle_in;
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "a configured, running endpoint");

    // 8. Drop alone tears it down.
    cfg_valid=1; my_drop=1; step; idle_in;
    check(state === EP_DISABLED, "a Drop flag tears the endpoint down");
    check(n_disabled === 32'd1, "one teardown");

    // 9. THE case. Drop AND Add in the same command is a RE-INITIALISATION,
    //    because drops are applied first. The endpoint ends up configured.
    cfg_valid=1; my_add=1; step; idle_in;
    db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_RUNNING, "running again");
    cfg_valid=1; my_drop=1; my_add=1; #1;
    check(reinitialised, "drop and add together is a re-initialisation");
    step; idle_in;
    check(state === EP_STOPPED,
          "so the endpoint ends up CONFIGURED, not disabled -- drops go first");
    check(n_reinit === 32'd1, "one re-initialisation");
    check(!usable, "freshly re-initialised, so stopped rather than running");

    // 10. D0 is not a legal drop. The slot context cannot be dropped by a
    //     Configure Endpoint command.
    hard_reset;
    is_slot_ctx=1;
    cfg_valid=1; my_add=1; step; idle_in;
    check(state === EP_STOPPED, "the slot context can be ADDED");
    cfg_valid=1; my_drop=1; #1;
    check(illegal_drop, "but a DROP of the slot context is illegal");
    step; idle_in;
    check(state === EP_STOPPED,
          "and is ignored -- the slot survives a routine reconfiguration");
    check(n_illegal_drop === 32'd1, "and the driver bug was counted");

    // 11. A disabled endpoint cannot error -- it is not running anything.
    hard_reset;
    is_slot_ctx=0;
    ep_error=1; step; idle_in;
    check(state === EP_DISABLED, "a DISABLED endpoint does not halt on error");
    check(n_halts === 32'd0, "and no halt was counted");

    // 12. A command outranks a doorbell in the same cycle.
    hard_reset;
    cfg_valid=1; my_add=1; step; idle_in;
    cfg_valid=1; my_drop=1; db_valid=1; db_is_mine=1; step; idle_in;
    check(state === EP_DISABLED,
          "a Configure Endpoint command outranks a doorbell in the same cycle");

    // ===== D. randomised =====
    hard_reset;
    for (i=0;i<40000;i=i+1) begin
      cfg_valid   = ($urandom%4)==0;
      my_drop     = $urandom%2;
      my_add      = $urandom%2;
      is_slot_ctx = ($urandom%8)==0;
      db_valid    = $urandom%2;
      db_is_mine  = $urandom%2;
      ep_error    = ($urandom%12)==0;
      reset_ep    = ($urandom%10)==0;
      step;
    end

    foreach (n_vis[i])
      check(n_vis[i] > 500, "every endpoint state was visited many times");
    check(n_reinit_seen > 500, "re-initialisations happened often");
    check(n_dbig        > 500, "doorbells were correctly ignored often");
    check(n_ill         > 200, "illegal slot drops were presented often");
    check(n_halt        > 200, "halts happened often");

    $display("");
    $display("  REACH: transitions=%0d | states: disabled=%0d stopped=%0d running=%0d halted=%0d",
             n_exh, n_vis[0], n_vis[1], n_vis[2], n_vis[3]);
    $display("  CASES: re-initialisations=%0d ignored-doorbells=%0d illegal-slot-drops=%0d halts=%0d",
             n_reinit_seen, n_dbig, n_ill, n_halt);
    $display("  COUNTERS: configured=%0d disabled=%0d reinit=%0d db-ignored=%0d illegal=%0d halts=%0d",
             n_configured, n_disabled, n_reinit, n_db_ignored,
             n_illegal_drop, n_halts);
    $display("  [SystemVerilog] xhci_ep_context: %0d errors", errors);
    $display("  [SystemVerilog] %0s", errors==0 ? "PASS" : "FAIL");
    $display("");
    $finish;
  end
endmodule

10.3 VHDL testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.xhci_ctx_pkg.all;

entity tb_ec_vhdl is
end entity;

architecture sim of tb_ec_vhdl is
  signal clk   : std_logic := '0';
  signal rst_n : std_logic := '0';
  signal cfg_valid, my_drop, my_add, is_slot_ctx : std_logic := '0';
  signal db_valid, db_is_mine, ep_error, reset_ep : std_logic := '0';

  signal state : std_logic_vector(2 downto 0);
  signal usable, reinitialised, illegal_drop, db_ignored : std_logic;
  signal n_configured, n_disabled, n_reinit, n_db_ignored,
         n_illegal_drop, n_halts : std_logic_vector(31 downto 0);

  signal running : boolean := true;

  type cnt4_t is array (0 to 3) of integer;
begin
  clk <= not clk after 5 ns when running else '0';

  dut : entity work.xhci_ep_context
    port map (clk => clk, rst_n => rst_n, cfg_valid => cfg_valid,
              my_drop => my_drop, my_add => my_add,
              is_slot_ctx => is_slot_ctx, db_valid => db_valid,
              db_is_mine => db_is_mine, ep_error => ep_error,
              reset_ep => reset_ep, state => state, usable => usable,
              reinitialised => reinitialised, illegal_drop => illegal_drop,
              db_ignored => db_ignored, n_configured => n_configured,
              n_disabled => n_disabled, n_reinit => n_reinit,
              n_db_ignored => n_db_ignored,
              n_illegal_drop => n_illegal_drop, n_halts => n_halts);

  stim : process
    variable seed1 : positive := 5119;
    variable seed2 : positive := 7727;
    variable r1    : real;

    -- VHDL-2008 requires a shared variable to have a protected type, so the
    -- bookkeeping lives inside the single stimulus process instead.
    variable errors : integer := 0;

    -- SHADOW MODEL of the endpoint state.
    variable s_st : ep_state_t := EP_DISABLED;
    variable m_cfg, m_dis, m_re, m_dbi, m_ill, m_hal : integer := 0;

    variable n_exh : integer := 0;
    variable n_vis : cnt4_t := (others => 0);
    variable n_reinit_seen, n_dbig, n_ill, n_halt : integer := 0;

    procedure check(cond : boolean; msg : string) is
    begin
      if not cond then
        errors := errors + 1;
        if errors <= 25 then
          report "  FAIL: " & msg
               & " (cfg=" & std_logic'image(cfg_valid)(2)
               & " drop=" & std_logic'image(my_drop)(2)
               & " add=" & std_logic'image(my_add)(2)
               & " slot=" & std_logic'image(is_slot_ctx)(2)
               & " db=" & std_logic'image(db_valid)(2)
               & " mine=" & std_logic'image(db_is_mine)(2)
               & " err=" & std_logic'image(ep_error)(2)
               & " rst=" & std_logic'image(reset_ep)(2)
               & " | state=" & integer'image(to_integer(unsigned(state)))
               & " usable=" & std_logic'image(usable)(2)
               & " reinit=" & std_logic'image(reinitialised)(2)
               & " illegal=" & std_logic'image(illegal_drop)(2)
               & " dbig=" & std_logic'image(db_ignored)(2)
               & " || model=" & integer'image(ep_state_t'pos(s_st))
               & ")" severity note;
        end if;
      end if;
    end procedure;

    procedure rnd(variable v : out integer; m : integer) is
    begin
      uniform(seed1, seed2, r1);
      v := integer(floor(r1 * real(m)));
    end procedure;

    -- The model is a flat case over the CURRENT state where the design
    -- applies drop and add as two successive assignments -- a different
    -- route, and one that cannot share an ordering bug with it.
    procedure model(variable e_st : out ep_state_t;
                    variable e_reinit, e_ill, e_dbig : out boolean) is
      variable dropa, adda, dbme : boolean;
    begin
      e_ill  := cfg_valid = '1' and is_slot_ctx = '1' and my_drop = '1';
      dropa  := cfg_valid = '1' and my_drop = '1' and is_slot_ctx = '0';
      adda   := cfg_valid = '1' and my_add = '1';
      dbme   := db_valid = '1' and db_is_mine = '1';
      e_reinit := dropa and adda;
      e_dbig   := dbme and s_st /= EP_STOPPED;

      e_st := s_st;
      if cfg_valid = '1' then
        -- stated as the four cases rather than as two assignments
        if dropa and adda then    e_st := EP_STOPPED;   -- re-initialised
        elsif dropa then          e_st := EP_DISABLED;
        elsif adda then           e_st := EP_STOPPED;
        end if;
      elsif ep_error = '1' then
        case s_st is
          when EP_DISABLED => e_st := EP_DISABLED;
          when others      => e_st := EP_HALTED;
        end case;
      elsif reset_ep = '1' then
        if s_st = EP_HALTED then e_st := EP_STOPPED; end if;
      elsif dbme then
        if s_st = EP_STOPPED then e_st := EP_RUNNING; end if;
      end if;
    end procedure;

    procedure check_comb is
      variable e_st : ep_state_t;
      variable e_reinit, e_ill, e_dbig : boolean;
    begin
      model(e_st, e_reinit, e_ill, e_dbig);

      check(state = st_code(s_st),          "state matches the shadow model");
      check((usable = '1') = (s_st = EP_RUNNING), "usable matches the state");
      check((reinitialised = '1') = e_reinit,
            "reinitialised matches the model");
      check((illegal_drop = '1') = e_ill,   "illegal_drop matches the model");
      check((db_ignored = '1') = e_dbig,    "db_ignored matches the model");

      -- ---- SAFETY PROPERTIES, independent of the model ----
      -- 1. THE property. Drop before Add: an endpoint named by BOTH masks is
      --    re-initialised, not torn down.
      if cfg_valid = '1' and my_drop = '1' and my_add = '1'
         and is_slot_ctx = '0' then
        check(reinitialised = '1',
              "drop+add in one command was not reported as a re-initialisation");
      end if;
      -- 2. A doorbell is a HINT.
      if db_valid = '1' and db_is_mine = '1' and cfg_valid = '0'
         and ep_error = '0' and reset_ep = '0' then
        if state = st_code(EP_DISABLED) then
          check(db_ignored = '1',
                "a doorbell on a DISABLED endpoint was not ignored");
        end if;
        if state = st_code(EP_HALTED) then
          check(db_ignored = '1',
                "a doorbell on a HALTED endpoint was not ignored -- the reset that clears the error has not happened");
        end if;
      end if;
      -- 3. D0 is not a legal drop.
      if cfg_valid = '1' and is_slot_ctx = '1' and my_drop = '1' then
        check(illegal_drop = '1',
              "a drop of the SLOT context was not flagged as illegal");
      end if;
      -- 4. Only a RUNNING endpoint is usable.
      check((usable = '1') = (state = st_code(EP_RUNNING)),
            "usable disagrees with the state");
      -- 5. A disabled endpoint runs nothing.
      if state = st_code(EP_DISABLED) then
        check(usable = '0', "a DISABLED endpoint reported itself usable");
      end if;
      -- 6. A halted endpoint runs nothing either.
      if state = st_code(EP_HALTED) then
        check(usable = '0', "a HALTED endpoint reported itself usable");
      end if;
      -- 7. The state is always one of the four that exist.
      check(to_integer(unsigned(state)) <= 3,
            "the endpoint reached an undefined state");
      -- 8. A re-initialisation is always also a drop and an add.
      if reinitialised = '1' then
        check(cfg_valid = '1' and my_drop = '1' and my_add = '1'
              and is_slot_ctx = '0',
              "a re-initialisation was reported with no drop+add pair");
      end if;

      n_vis(ep_state_t'pos(s_st)) := n_vis(ep_state_t'pos(s_st)) + 1;
      if e_reinit then n_reinit_seen := n_reinit_seen + 1; end if;
      if e_dbig   then n_dbig := n_dbig + 1; end if;
      if e_ill    then n_ill  := n_ill + 1;  end if;
      if e_st = EP_HALTED and s_st /= EP_HALTED then
        n_halt := n_halt + 1;
      end if;
    end procedure;

    procedure model_step is
      variable e_st : ep_state_t;
      variable e_reinit, e_ill, e_dbig : boolean;
    begin
      model(e_st, e_reinit, e_ill, e_dbig);
      if e_st /= EP_DISABLED and s_st = EP_DISABLED then
        m_cfg := m_cfg + 1;
      end if;
      if e_st = EP_DISABLED and s_st /= EP_DISABLED then
        m_dis := m_dis + 1;
      end if;
      if e_reinit then m_re  := m_re + 1;  end if;
      if e_dbig   then m_dbi := m_dbi + 1; end if;
      if e_ill    then m_ill := m_ill + 1; end if;
      if e_st = EP_HALTED and s_st /= EP_HALTED then
        m_hal := m_hal + 1;
      end if;
      s_st := e_st;
    end procedure;

    procedure step is
    begin
      wait for 1 ns;
      check_comb;
      model_step;
      wait until rising_edge(clk);
      wait for 1 ns;
      check(state = st_code(s_st), "state tracked the model");
      check(n_configured = std_logic_vector(to_unsigned(m_cfg, 32)),
            "n_configured matches the model");
      check(n_disabled = std_logic_vector(to_unsigned(m_dis, 32)),
            "n_disabled matches the model");
      check(n_reinit = std_logic_vector(to_unsigned(m_re, 32)),
            "n_reinit matches the model");
      check(n_db_ignored = std_logic_vector(to_unsigned(m_dbi, 32)),
            "n_db_ignored matches the model");
      check(n_illegal_drop = std_logic_vector(to_unsigned(m_ill, 32)),
            "n_illegal_drop matches the model");
      check(n_halts = std_logic_vector(to_unsigned(m_hal, 32)),
            "n_halts matches the model");
    end procedure;

    procedure idle_in is
    begin
      cfg_valid <= '0'; my_drop <= '0'; my_add <= '0'; db_valid <= '0';
      db_is_mine <= '0'; ep_error <= '0'; reset_ep <= '0';
    end procedure;

    procedure hard_reset is
    begin
      rst_n <= '0'; idle_in; is_slot_ctx <= '0';
      wait until rising_edge(clk); wait for 1 ns;
      wait until rising_edge(clk); wait for 1 ns;
      rst_n <= '1'; wait for 1 ns;
      s_st := EP_DISABLED;
      m_cfg := 0; m_dis := 0; m_re := 0; m_dbi := 0; m_ill := 0; m_hal := 0;
    end procedure;

    -- Walk to a chosen state using only legal transitions: Configure
    -- Endpoint to leave DISABLED, a doorbell to start, an error to halt.
    procedure goto_state(want : ep_state_t) is
    begin
      hard_reset;
      is_slot_ctx <= '0';
      if want /= EP_DISABLED then
        cfg_valid <= '1'; my_add <= '1'; step; idle_in;      -- -> STOPPED
        if want = EP_RUNNING then
          db_valid <= '1'; db_is_mine <= '1'; step; idle_in; -- -> RUNNING
        elsif want = EP_HALTED then
          ep_error <= '1'; step; idle_in;                    -- -> HALTED
        end if;
      end if;
      wait for 1 ns;
      check(state = st_code(want), "goto_state reached the requested state");
    end procedure;

    variable iv : integer;
    variable ba, bb, bc, bd, be, bf, bg, bh : std_logic;
  begin
    hard_reset;
    check(state = st_code(EP_DISABLED), "reset leaves the endpoint DISABLED");
    check(usable = '0', "and unusable");

    -- ===== A. EXHAUSTIVE sweep =====
    -- 4 endpoint states x cfg_valid x my_drop x my_add x is_slot_ctx
    --   x db_valid x db_is_mine x ep_error x reset_ep
    -- = 4 x 256 = 1024 one-step transitions.
    for st0 in 0 to 3 loop
      for a in 0 to 1 loop
        if a = 1 then ba := '1'; else ba := '0'; end if;
        for b in 0 to 1 loop
          if b = 1 then bb := '1'; else bb := '0'; end if;
          for c in 0 to 1 loop
            if c = 1 then bc := '1'; else bc := '0'; end if;
            for d in 0 to 1 loop
              if d = 1 then bd := '1'; else bd := '0'; end if;
              for e in 0 to 1 loop
                if e = 1 then be := '1'; else be := '0'; end if;
                for f in 0 to 1 loop
                  if f = 1 then bf := '1'; else bf := '0'; end if;
                  for g in 0 to 1 loop
                    if g = 1 then bg := '1'; else bg := '0'; end if;
                    for h in 0 to 1 loop
                      if h = 1 then bh := '1'; else bh := '0'; end if;
                      goto_state(ep_state_t'val(st0));
                      cfg_valid <= ba; my_drop <= bb; my_add <= bc;
                      is_slot_ctx <= bd; db_valid <= be; db_is_mine <= bf;
                      ep_error <= bg; reset_ep <= bh;
                      step;
                      n_exh := n_exh + 1;
                      idle_in;
                    end loop;
                  end loop;
                end loop;
              end loop;
            end loop;
          end loop;
        end loop;
      end loop;
    end loop;
    report "  exhaustive endpoint-context sweep: " & integer'image(n_exh)
         & " of 1024 transitions verified" severity note;

    -- ===== B. directed: the life of an endpoint =====
    hard_reset;

    -- 1. Configure Endpoint with only the Add flag.
    cfg_valid <= '1'; my_add <= '1'; step; idle_in;
    check(state = st_code(EP_STOPPED), "an Add flag configures the endpoint");
    check(usable = '0',
          "but it is not running yet -- nothing has rung its doorbell");
    check(n_configured = std_logic_vector(to_unsigned(1, 32)),
          "one configuration");

    -- 2. A doorbell starts it.
    db_valid <= '1'; db_is_mine <= '1'; step; idle_in;
    check(state = st_code(EP_RUNNING), "a doorbell starts a STOPPED endpoint");
    check(usable = '1', "and now transfers may run");
    check(n_db_ignored = std_logic_vector(to_unsigned(0, 32)),
          "that doorbell was not ignored");

    -- 3. A doorbell for someone ELSE does nothing.
    db_valid <= '1'; db_is_mine <= '0'; step; idle_in;
    check(state = st_code(EP_RUNNING),
          "a doorbell for another endpoint changes nothing");
    check(n_db_ignored = std_logic_vector(to_unsigned(0, 32)),
          "and is not even counted as ignored -- it was never ours");

    -- 4. A transfer errors: the endpoint halts.
    ep_error <= '1'; step; idle_in;
    check(state = st_code(EP_HALTED), "an error halts the endpoint");
    check(usable = '0', "which stops transfers");
    check(n_halts = std_logic_vector(to_unsigned(1, 32)), "one halt");

    -- 5. THE doorbell rule.
    db_valid <= '1'; db_is_mine <= '1'; wait for 1 ns;
    check(db_ignored = '1',
          "a doorbell on a HALTED endpoint is IGNORED -- it is a hint, not a command");
    step; idle_in;
    check(state = st_code(EP_HALTED), "the endpoint is still halted");
    check(n_db_ignored = std_logic_vector(to_unsigned(1, 32)),
          "and the ignored doorbell was counted");

    -- 6. Reset Endpoint clears the halt.
    reset_ep <= '1'; step; idle_in;
    check(state = st_code(EP_STOPPED), "Reset Endpoint clears the halt");
    check(usable = '0', "leaving it stopped, not running");

    -- 7. Reset Endpoint from a non-halted state does nothing.
    db_valid <= '1'; db_is_mine <= '1'; step; idle_in;
    check(state = st_code(EP_RUNNING), "started again");
    reset_ep <= '1'; step; idle_in;
    check(state = st_code(EP_RUNNING),
          "Reset Endpoint on a RUNNING endpoint does nothing");

    -- ===== C. directed: drop, add, and the order between them =====
    hard_reset;
    cfg_valid <= '1'; my_add <= '1'; step; idle_in;
    db_valid <= '1'; db_is_mine <= '1'; step; idle_in;
    check(state = st_code(EP_RUNNING), "a configured, running endpoint");

    -- 8. Drop alone tears it down.
    cfg_valid <= '1'; my_drop <= '1'; step; idle_in;
    check(state = st_code(EP_DISABLED),
          "a Drop flag tears the endpoint down");
    check(n_disabled = std_logic_vector(to_unsigned(1, 32)), "one teardown");

    -- 9. THE case. Drop AND Add together is a RE-INITIALISATION.
    cfg_valid <= '1'; my_add <= '1'; step; idle_in;
    db_valid <= '1'; db_is_mine <= '1'; step; idle_in;
    check(state = st_code(EP_RUNNING), "running again");
    cfg_valid <= '1'; my_drop <= '1'; my_add <= '1'; wait for 1 ns;
    check(reinitialised = '1',
          "drop and add together is a re-initialisation");
    step; idle_in;
    check(state = st_code(EP_STOPPED),
          "so the endpoint ends up CONFIGURED, not disabled -- drops go first");
    check(n_reinit = std_logic_vector(to_unsigned(1, 32)),
          "one re-initialisation");
    check(usable = '0',
          "freshly re-initialised, so stopped rather than running");

    -- 10. D0 is not a legal drop.
    hard_reset;
    is_slot_ctx <= '1';
    cfg_valid <= '1'; my_add <= '1'; step; idle_in;
    check(state = st_code(EP_STOPPED), "the slot context can be ADDED");
    cfg_valid <= '1'; my_drop <= '1'; wait for 1 ns;
    check(illegal_drop = '1',
          "but a DROP of the slot context is illegal");
    step; idle_in;
    check(state = st_code(EP_STOPPED),
          "and is ignored -- the slot survives a routine reconfiguration");
    check(n_illegal_drop = std_logic_vector(to_unsigned(1, 32)),
          "and the driver bug was counted");

    -- 11. A disabled endpoint cannot error.
    hard_reset;
    is_slot_ctx <= '0';
    ep_error <= '1'; step; idle_in;
    check(state = st_code(EP_DISABLED),
          "a DISABLED endpoint does not halt on error");
    check(n_halts = std_logic_vector(to_unsigned(0, 32)),
          "and no halt was counted");

    -- 12. A command outranks a doorbell in the same cycle.
    hard_reset;
    cfg_valid <= '1'; my_add <= '1'; step; idle_in;
    cfg_valid <= '1'; my_drop <= '1'; db_valid <= '1'; db_is_mine <= '1';
    step; idle_in;
    check(state = st_code(EP_DISABLED),
          "a Configure Endpoint command outranks a doorbell in the same cycle");

    -- ===== D. randomised =====
    -- ieee.math_real.uniform is a genuinely different generator from either
    -- Verilog builtin, which is what makes this column independent evidence.
    hard_reset;
    for i in 0 to 39999 loop
      rnd(iv, 4);  if iv = 0 then cfg_valid <= '1';
                   else cfg_valid <= '0'; end if;
      rnd(iv, 2);  if iv = 1 then my_drop <= '1'; else my_drop <= '0'; end if;
      rnd(iv, 2);  if iv = 1 then my_add <= '1';  else my_add <= '0';  end if;
      rnd(iv, 8);  if iv = 0 then is_slot_ctx <= '1';
                   else is_slot_ctx <= '0'; end if;
      rnd(iv, 2);  if iv = 1 then db_valid <= '1';
                   else db_valid <= '0'; end if;
      rnd(iv, 2);  if iv = 1 then db_is_mine <= '1';
                   else db_is_mine <= '0'; end if;
      rnd(iv, 12); if iv = 0 then ep_error <= '1';
                   else ep_error <= '0'; end if;
      rnd(iv, 10); if iv = 0 then reset_ep <= '1';
                   else reset_ep <= '0'; end if;
      step;
    end loop;

    for i in 0 to 3 loop
      check(n_vis(i) > 500, "every endpoint state was visited many times");
    end loop;
    check(n_reinit_seen > 500, "re-initialisations happened often");
    check(n_dbig        > 500, "doorbells were correctly ignored often");
    check(n_ill         > 200, "illegal slot drops were presented often");
    check(n_halt        > 200, "halts happened often");

    report "  REACH: transitions=" & integer'image(n_exh)
         & " | states: disabled=" & integer'image(n_vis(0))
         & " stopped=" & integer'image(n_vis(1))
         & " running=" & integer'image(n_vis(2))
         & " halted=" & integer'image(n_vis(3)) severity note;
    report "  CASES: re-initialisations=" & integer'image(n_reinit_seen)
         & " ignored-doorbells=" & integer'image(n_dbig)
         & " illegal-slot-drops=" & integer'image(n_ill)
         & " halts=" & integer'image(n_halt) severity note;
    report "  COUNTERS: configured="
         & integer'image(to_integer(unsigned(n_configured)))
         & " disabled=" & integer'image(to_integer(unsigned(n_disabled)))
         & " reinit=" & integer'image(to_integer(unsigned(n_reinit)))
         & " db-ignored=" & integer'image(to_integer(unsigned(n_db_ignored)))
         & " illegal=" & integer'image(to_integer(unsigned(n_illegal_drop)))
         & " halts=" & integer'image(to_integer(unsigned(n_halts)))
         severity note;
    report "  [VHDL] xhci_ep_context: " & integer'image(errors) & " errors"
         severity note;
    if errors = 0 then
      report "  [VHDL] PASS" severity note;
    else
      report "  [VHDL] FAIL" severity failure;
    end if;
    running <= false;
    wait;
  end process;
end architecture;

11. Exhaustive Verification and Mutation Testing

MeasureVerilogSystemVerilogVHDL
Exhaustive transitions1024 / 10241024 / 10241024 / 1024
EP_DISABLED visits127051336513320
EP_STOPPED visits145311420314120
EP_RUNNING visits893289859227
EP_HALTED visits615557705656
re-initialisations225822682211
doorbells correctly ignored663468966914
illegal slot drops presented734741721
halts186117791764
ResultPASSPASSPASS
#MutationVerilogSysVerVHDL
P1Add is applied before Drop190380190427188861
P2a doorbell starts a DISABLED endpoint173842174255175720
P3a doorbell starts a HALTED endpoint861968511685212
P4D0 is honoured — the slot context is dropped202237202246202329
P5an error halts a DISABLED endpoint165824165069165616
P6Reset Endpoint works from any state442364414644109
P7the doorbell outranks the command170761170664170183
—unmutated baseline000

All seven die in all three languages, all counts distinct, columns within 1.5%.

P1 is the mutation this chapter is about, at ~190 000, and it is two lines swapped. Note that it scores less than P4 (~202 000): honouring D0 is wrong on a larger fraction of the input space, because is_slot_ctx and my_drop are independent, whereas P1 only differs when both masks name the same endpoint.

P6 is the smallest at ~44 000, and it is the one worth dwelling on. Reset Endpoint working from any state sounds harmless — even helpful. What it actually does is silently stop a running endpoint whenever software issues a reset for an endpoint it believed was halted and which has since recovered. The transfer in progress is abandoned with no completion event. It scores low because it only differs when a reset arrives at a non-halted endpoint, which is a narrow condition and exactly the racy one.

P2 and P3 are the two halves of "a doorbell is a hint", and their counts differ by a factor of two (174 000 against 86 000) for a structural reason: DISABLED is visited about twice as often as HALTED in the sweep, because it is the reset state and the destination of every drop. A mutation's count is the size of the population it is wrong on, not the severity of being wrong there — and P3, the smaller of the two, is the more dangerous, because it defeats an error-recovery mechanism rather than merely starting something that does not exist.

12. Debugging Walkthrough: The Endpoint That Vanishes on Reconfiguration

The report. A USB audio device works perfectly until the user changes the sample rate. After that, the device is present, enumerated and completely silent. Unplugging and replugging fixes it.

Step 1 — what does a sample-rate change do? It changes an isochronous endpoint's max packet size, which is in the endpoint context, which hardware owns. Software cannot edit it in place, so the driver issues a Configure Endpoint command with the endpoint in both masks — drop it and add it back with the new packet size, atomically.

Step 2 — what state is the endpoint in afterwards? Read the Output Context. EP_DISABLED. The endpoint does not exist.

Step 3 — is the driver's command right? Check the Input Context it built: D and A both set for that endpoint, which is exactly the documented way to re-initialise one. The driver is correct.

Step 4 — so what did the controller do with it? The specification says drops are applied first, then adds, so the endpoint should end STOPPED. Ending DISABLED means the adds were applied first and the drop overwrote them.

Step 5 — why replugging fixes it. A fresh enumeration configures the endpoint with a command that has only the Add flag set — no drop — so the ordering never matters. The bug requires both flags in one command, which happens only on reconfiguration, which happens only when the user changes something.

Step 6 — the cause. Two assignments in the wrong order. Mutation P1, in production.

13. UVM and Assertions

13.1 The transaction and the sequence that matters

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class xhci_ctx_item extends uvm_sequence_item;
  `uvm_object_utils(xhci_ctx_item)

  rand bit cfg_valid;
  rand bit my_drop;
  rand bit my_add;
  rand bit is_slot_ctx;
  rand bit db_valid;
  rand bit db_is_mine;
  rand bit ep_error;
  rand bit reset_ep;

  // Commands are rare; doorbells are constant. That is what the traffic on
  // a real controller looks like, and it is why the interesting command
  // cases need their own sequences rather than waiting for the randomiser.
  constraint c_realistic {
    cfg_valid   dist {0 := 3, 1 := 1};
    db_valid    dist {1 := 1, 0 := 1};
    ep_error    dist {0 := 11, 1 := 1};
    reset_ep    dist {0 := 9,  1 := 1};
    is_slot_ctx dist {0 := 7,  1 := 1};
  }

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

  function string convert2string();
    return $sformatf("cfg=%0b D=%0b A=%0b slot=%0b db=%0b/%0b err=%0b rst=%0b",
                     cfg_valid, my_drop, my_add, is_slot_ctx, db_valid,
                     db_is_mine, ep_error, reset_ep);
  endfunction
endclass

// THE sequence for this chapter. It reproduces what a driver does when it
// changes an endpoint's packet size: configure, start, run, and then issue
// ONE command with both the Drop and Add flags set. A correct controller
// leaves the endpoint configured; a controller that applies Add first leaves
// it disabled, and the device goes silent.
class reconfigure_endpoint_seq extends uvm_sequence #(xhci_ctx_item);
  `uvm_object_utils(reconfigure_endpoint_seq)
  function new(string name = "reconfigure_endpoint_seq"); super.new(name); endfunction

  task body();
    repeat (300) begin
      xhci_ctx_item it;

      // configure it
      it = xhci_ctx_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { cfg_valid == 1; my_add == 1; my_drop == 0;
                                 is_slot_ctx == 0; db_valid == 0;
                                 ep_error == 0; reset_ep == 0; })
        `uvm_error("RAND", "configure randomize failed")
      finish_item(it);

      // start it
      it = xhci_ctx_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { cfg_valid == 0; db_valid == 1;
                                 db_is_mine == 1; ep_error == 0;
                                 reset_ep == 0; })
        `uvm_error("RAND", "doorbell randomize failed")
      finish_item(it);

      // ...and re-initialise it in ONE command
      it = xhci_ctx_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { cfg_valid == 1; my_drop == 1; my_add == 1;
                                 is_slot_ctx == 0; db_valid == 0;
                                 ep_error == 0; reset_ep == 0; })
        `uvm_error("RAND", "reinit randomize failed")
      finish_item(it);
    end
  endtask
endclass

// An optimistic driver: it rings doorbells without reading back a state that
// hardware owns. Every one of these on a DISABLED or HALTED endpoint must do
// nothing -- which is the doorbell-is-a-hint rule, and the two mutations
// that break it.
class optimistic_doorbell_seq extends uvm_sequence #(xhci_ctx_item);
  `uvm_object_utils(optimistic_doorbell_seq)
  function new(string name = "optimistic_doorbell_seq"); super.new(name); endfunction

  task body();
    repeat (500) begin
      xhci_ctx_item it = xhci_ctx_item::type_id::create("it");
      start_item(it);
      it.c_realistic.constraint_mode(0);
      if (!it.randomize() with { cfg_valid == 0; db_valid == 1;
                                 db_is_mine == 1; ep_error == 0;
                                 reset_ep == 0; })
        `uvm_error("RAND", "optimistic randomize failed")
      finish_item(it);
    end
  endtask
endclass

13.2 The scoreboard's three checks

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class xhci_ctx_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(xhci_ctx_scoreboard)

  uvm_analysis_imp #(xhci_ctx_mon_item, xhci_ctx_scoreboard) ap;

  ep_state_e   sb_state;
  int unsigned n_reinit, n_db_ignored, n_illegal, n_halts;

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

  function void write(xhci_ctx_mon_item t);
    ep_state_e prev = sb_state;
    bit dropa = t.cfg_valid && t.my_drop && !t.is_slot_ctx;
    bit adda  = t.cfg_valid && t.my_add;
    bit dbme  = t.db_valid  && t.db_is_mine;

    // ---- THE property. Drop before Add: both flags is a RE-INIT. ----
    if (dropa && adda) begin
      if (!t.reinitialised)
        `uvm_error("REINIT",
          "drop+add was not reported as a re-initialisation")
      n_reinit++;
    end

    // ---- A doorbell is a HINT, not a command ----
    if (dbme && !t.cfg_valid && !t.ep_error && !t.reset_ep) begin
      if ((prev == EP_DISABLED) || (prev == EP_HALTED)) begin
        if (!t.db_ignored)
          `uvm_error("DOORBELL",
            $sformatf("a doorbell at a %s endpoint was not ignored -- software is allowed to be optimistic and hardware must re-check",
                      prev.name()))
        n_db_ignored++;
      end
    end

    // ---- D0 is never honoured ----
    if (t.cfg_valid && t.is_slot_ctx && t.my_drop) begin
      if (!t.illegal_drop)
        `uvm_error("SLOT",
          "a drop of the SLOT context was not flagged -- honouring it destroys the device's addressing")
      n_illegal++;
    end

    // ---- Advance the scoreboard's own state, by the spec's rules ----
    if (t.cfg_valid) begin
      // drop FIRST, then add -- stated as the four cases, so the scoreboard
      // cannot share an ordering bug with the design
      if      (dropa && adda) sb_state = EP_STOPPED;
      else if (dropa)         sb_state = EP_DISABLED;
      else if (adda)          sb_state = EP_STOPPED;
    end else if (t.ep_error) begin
      if (prev != EP_DISABLED) begin sb_state = EP_HALTED; n_halts++; end
    end else if (t.reset_ep) begin
      if (prev == EP_HALTED) sb_state = EP_STOPPED;
    end else if (dbme) begin
      if (prev == EP_STOPPED) sb_state = EP_RUNNING;
    end

    if (t.state !== sb_state)
      `uvm_error("STATE", $sformatf("state=%s, scoreboard=%s",
                                    t.state.name(), sb_state.name()))
    if (t.usable !== (sb_state == EP_RUNNING))
      `uvm_error("USABLE", "usable disagrees with the state");
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("SB", $sformatf(
      "reinits=%0d ignored-doorbells=%0d illegal-drops=%0d halts=%0d",
      n_reinit, n_db_ignored, n_illegal, n_halts), UVM_LOW)

    if (n_reinit     == 0) `uvm_error("COVERAGE",
      "no drop+add command was ever issued -- the ordering rule this chapter is about is untested")
    if (n_db_ignored == 0) `uvm_error("COVERAGE",
      "no doorbell was ever rung at a DISABLED or HALTED endpoint")
    if (n_illegal    == 0) `uvm_error("COVERAGE",
      "D0 was never presented")
  endfunction
endclass

13.3 SystemVerilog assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module xhci_ep_context_sva
  import xhci_ctx_pkg::*;
(
  input logic      clk,
  input logic      rst_n,
  input logic      cfg_valid,
  input logic      my_drop,
  input logic      my_add,
  input logic      is_slot_ctx,
  input logic      db_valid,
  input logic      db_is_mine,
  input logic      ep_error,
  input logic      reset_ep,
  input ep_state_e state,
  input logic      usable,
  input logic      reinitialised,
  input logic      illegal_drop,
  input logic      db_ignored
);
  default clocking cb @(posedge clk); endclocking
  default disable iff (!rst_n);

  // ---- 1. THE property. Drop before Add: an endpoint named by BOTH masks
  // ----    ends up CONFIGURED, not torn down.
  property p_drop_before_add;
    (cfg_valid && my_drop && my_add && !is_slot_ctx)
      |=> (state == EP_STOPPED);
  endproperty
  a_drop_before_add : assert property (p_drop_before_add)
    else $error("drop+add left the endpoint %s -- the adds were applied first, and a routine reconfiguration just disabled the endpoint",
                state.name());

  // ---- 2. A doorbell NEVER starts an endpoint that is not STOPPED. ----
  property p_doorbell_is_a_hint;
    (db_valid && db_is_mine && !cfg_valid && !ep_error && !reset_ep
     && (state != EP_STOPPED)) |=> $stable(state);
  endproperty
  a_doorbell_is_a_hint : assert property (p_doorbell_is_a_hint)
    else $error("a doorbell changed the state of a %s endpoint -- it is a hint, not a command",
                $past(state.name()));

  // ---- 3. ...and it is reported as ignored, so the distinction between
  // ----    "nothing to do" and "wrong question" is visible.
  property p_ignored_is_reported;
    db_ignored == (db_valid && db_is_mine && (state != EP_STOPPED));
  endproperty
  a_ignored_is_reported : assert property (p_ignored_is_reported);

  // ---- 4. D0 is never honoured. ----
  property p_slot_never_dropped;
    (cfg_valid && is_slot_ctx && my_drop && !my_add) |=> $stable(state);
  endproperty
  a_slot_never_dropped : assert property (p_slot_never_dropped)
    else $error("the SLOT context was dropped by a Configure Endpoint command -- the device's addressing is gone");

  property p_illegal_drop_reported;
    illegal_drop == (cfg_valid && is_slot_ctx && my_drop);
  endproperty
  a_illegal_drop_reported : assert property (p_illegal_drop_reported);

  // ---- 5. Only a RUNNING endpoint is usable. ----
  property p_usable_iff_running;
    usable == (state == EP_RUNNING);
  endproperty
  a_usable_iff_running : assert property (p_usable_iff_running);

  // ---- 6. A DISABLED endpoint cannot halt: it is running nothing. ----
  property p_disabled_cannot_halt;
    ((state == EP_DISABLED) && ep_error && !cfg_valid) |=> $stable(state);
  endproperty
  a_disabled_cannot_halt : assert property (p_disabled_cannot_halt);

  // ---- 7. Reset Endpoint clears a halt and ONLY a halt. ----
  property p_reset_only_clears_a_halt;
    (reset_ep && !cfg_valid && !ep_error && (state != EP_HALTED))
      |=> $stable(state);
  endproperty
  a_reset_only_clears_a_halt :
    assert property (p_reset_only_clears_a_halt)
    else $error("Reset Endpoint changed a %s endpoint -- a transfer in progress was abandoned with no completion event",
                $past(state.name()));

  // ---- 8. A command outranks a doorbell in the same cycle. ----
  property p_command_outranks_doorbell;
    (cfg_valid && my_drop && !my_add && !is_slot_ctx && db_valid
     && db_is_mine) |=> (state == EP_DISABLED);
  endproperty
  a_command_outranks_doorbell :
    assert property (p_command_outranks_doorbell);

  // ---- Cover: the interesting populations were reached. ----
  c_reinit      : cover property ((reinitialised));
  c_db_disabled : cover property ((db_valid && db_is_mine
                                   && (state == EP_DISABLED)));
  c_db_halted   : cover property ((db_valid && db_is_mine
                                   && (state == EP_HALTED)));
  c_slot_drop   : cover property ((illegal_drop));
endmodule

bind xhci_ep_context xhci_ep_context_sva u_sva (.*);

14. Common Misconceptions

"Software can edit an endpoint context." Hardware owns it and writes to it. That is why there are two contexts and a command to transfer between them.

"Setting Drop and Add for the same endpoint is contradictory." It is a re-initialisation, and it is the documented way to change an endpoint's packet size or ring pointer atomically.

"The order of Drop and Add does not matter — they are different endpoints usually." Usually. The one case where it matters is the one a driver reaches on every reconfiguration.

"D0 drops the slot." D0 in a Configure Endpoint command is illegal and ignored. The slot is torn down by Disable Slot.

"A doorbell starts an endpoint." It says there may be work. It starts a STOPPED endpoint and does nothing at all to a DISABLED or HALTED one.

"Ringing a doorbell at a halted endpoint is a driver bug." It is legal and expected — software rings after enqueueing without reading back state it does not own. Hardware re-checks.

"Reset Endpoint puts an endpoint back to idle." It clears a halt, and only a halt. Applying it to a running endpoint abandons a transfer with no completion event.

"A disabled endpoint can error." It is not running anything that could fail.

15. Exercises

1. Swap the two assignments (mutation P1) and predict which of the eight safety properties fires first. Then explain why P1 scores less than P4 despite being the more subtle bug.

2. P3 (a doorbell starts a HALTED endpoint) scores half of P2 (a doorbell starts a DISABLED one). Show that the ratio is the ratio of state visits, and construct a stimulus that inverts it.

3. Extend the block to a full 32-context slot, with drop_flags[31:0] and add_flags[31:0]. What is the exhaustive state space now, and which of the eight SVA properties still hold unchanged?

4. Property 4 uses !my_add in its antecedent. Remove that term, run it against the correct design, and explain the failure. What does that tell you about writing a property for an input combination the specification says is illegal?

5. The design reports db_ignored as an output. Argue for and against exposing it, given that a correct system rings ignored doorbells constantly and the counter will therefore always be large.

6. reinitialised is an output that no other logic consumes. Is it dead? Apply the dead-versus-unreachable test from this curriculum's mutation discipline, and say what would change if it were removed.

16. Summary

IdeaWhy it matters
Hardware owns the endpoint contextsoftware cannot read-modify-write it
Two contexts: Input and Outputone identified moment of transfer
Drop flags are applied before Add flagsso drop+add is an atomic re-initialisation
Reverse them and the same command disables the endpointtwo lines, and a device that goes silent
D0 is not a legal dropthe slot holds the address; Disable Slot tears it down
A doorbell is a hintit starts a STOPPED endpoint and nothing else
Three states mean "not transferring"and each needs a different command
Reset Endpoint clears a halt, and only a haltotherwise it abandons a live transfer
A command outranks a doorbellthe command says whether the endpoint exists
1024-transition exhaustive verificationevery state × every input combination
7 mutations, all killed in 3 languagesP1 is two lines swapped

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o ec_v.out ec_v.v ec_v_tb.v && ./ec_v.out
SystemVerilogiverilog -g2012 -o ec_sv.out ec_sv.sv ec_sv_tb.sv && ./ec_sv.out
VHDL-2008 analysenvc --std=2008 -a ec_vhdl.vhd ec_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_ec_vhdl
VHDL-2008 runnvc --std=2008 -r tb_ec_vhdl
One mutationiverilog -g2005 -DMUT_P1 -o mm ec_v_mut.v ec_v_tb.v && ./mm

All three implementations pass with 0 errors: 1024 of 1024 exhaustive transitions, 40 000 randomised cycles, every endpoint state reached and asserted reached.

17. Module 22 Complete

Four chapters, four synthesisable blocks, twelve implementations, twenty-eight mutations — all killed in all three languages.

ChapterBlockThe structural idea
22.1ehci_async_walkera ring with no end; termination is a lap with no work
22.2xhci_trb_ringone bit per entry replaces every shared pointer
22.3usb_frame_scheduleryou cannot start what you cannot finish
22.4xhci_ep_contextownership transfers at a command, and drops go first

Read together, these four are about a single problem that the device side never has: the host controller and the driver are two agents editing the same memory at the same time, and neither can stop the other.

Every mechanism in the module is an answer to it. EHCI makes its list circular so that an edit is never observed half-done, and pays for it by needing the Reclamation flag to know when to stop. xHCI replaces the shared pointers with a cycle bit, so that nothing is shared but the array itself. The scheduler commits to a transaction before it starts, because it cannot take it back. And the context manager transfers ownership at a single command boundary, because there is no lock that spans a PCIe link.

None of these is a synchronisation primitive. There is no mutex, no atomic, no barrier anywhere in a USB host controller. What there is instead, four times over, is a structure in which the dangerous interleavings cannot be expressed — which is the only kind of synchronisation available when the other agent is a piece of silicon that will not wait for you.

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.