Skip to content
VLSI Mentor

USB · Module 23

Device Controller RTL

Every device on a USB bus sees every token, so the decode that answers 'is this mine?' is the only thing preventing two devices driving the same wire — and the select must be one-hot by construction.

Module 21 built a device controller from the protocol's rules. This module is about the RTL engineering underneath it — decode, arbitration, memory sizing, state-machine resynchronisation, and clock-domain crossing. Different concerns, different failure modes, and the first one starts at the very front of the device.

1. Every Device Sees Every Token

USB is a broadcast medium. A hub repeats a downstream token to every enabled port, so a token addressed to one device is physically received by all of them.

The only thing that stops six devices answering at once is that five of them look at the address field and say nothing.

2. The Address Compare Is an Equality, and Nothing Else

A device responds to exactly one address: its own. There is no wildcard, no broadcast address, and no "also respond to 0 just in case".

Address 0 feels like it should be special, and it is — but not here. A device in the DEFAULT state has address 0 (Chapter 21.5), so a plain equality already covers it.

3. The Address Is Seven Bits

Not eight. Not six.

A seven-bit compare done in six bits makes address 64 indistinguishable from address 0, so a device at address 64 answers the enumeration traffic meant for whatever was just plugged in.

That failure needs 65 devices on one bus before it can happen — which is why it survives bring-up, survives the lab, and appears at a customer site with a large hub tree. Mutation Q6.

4. A Non-Existent Endpoint Gets Silence

The second question the decode answers is "does the endpoint exist?", and the answer changes what the device does in a way that is easy to get backwards:

ConditionResponse
endpoint exists, haltedSTALL (Chapter 21.1)
endpoint does not existnothing at all

A STALL is a statement about an endpoint: "this one is halted." Sending it for an endpoint that does not exist tells the host the endpoint does exist and is merely stalled — so the host issues CLEAR_FEATURE(ENDPOINT_HALT) and tries again. For ever.

Silence lets the host's timeout conclude what is actually true.

Two questions, four outcomes — and three of them are 'drive nothing'

A token decode. The address is compared for equality against the device's own address. A mismatch means the token belongs to another device. A match then checks the endpoint-enable mask: an endpoint that exists produces a one-hot select, and one that does not produces silence.Token seenevery device sees itaddr == ours?7-bit equality, no wildcardendpoint exists?the ep_enabled maskTOK_SELECTEDone-hot select, one blockTOK_NOT_OURSanother device's businessTOK_NO_SUCH_EPSILENCE — never a STALLmatchyesdiffer12
TOK_IDLE, TOK_NOT_OURS and TOK_NO_SUCH_EP all look identical on the wire: the device is silent. They are three completely different facts, and only one of them is this device's responsibility.

5. And the Select Must Be One-Hot by Construction

Each endpoint is a block with its own state and its own FIFO port. They share a datapath, so two selects asserted at once is a contention inside the device — and unlike the bus contention in §1, nothing external will ever reveal it.

So the decode produces a one-hot-or-zero vector, and it is built by shifting a single bit:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign ep_select = ep_exists ? (1 << token_ep) : 0;

rather than by a set of comparisons:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// what NOT to do -- a mistake in one comparison produces CONTENTION
assign ep_select[0] = ep_exists && (token_ep == 0);
assign ep_select[1] = ep_exists && (token_ep == 1);
assign ep_select[2] = ep_exists && (token_ep == 1);   // typo: two hot
assign ep_select[3] = ep_exists && (token_ep == 3);

The difference is what a mistake costs. In the shift form a wrong index selects the wrong endpoint — bad, and visible as soon as the wrong endpoint's FIFO gets data. In the comparison form a wrong index selects two endpoints, and what appears on the shared datapath is the AND or the OR of two drivers, depending on the technology.

6. What We Are Building

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  usb_ep_decode  #(EP_N = 4, EPW = 2)

  inputs                        outputs
  ------                        -------
  dev_address [6:0]             for_us
  token_valid                   ep_select [3:0]   ONE-HOT or zero
  token_addr  [6:0]             ep_exists
  token_ep    [1:0]             unknown_ep
  ep_enabled  [3:0]             silent
                                verdict   IDLE / NOT_OURS /
                                          NO_SUCH_EP / SELECTED

  n_tokens / n_for_us / n_not_ours / n_unknown_ep / n_selected

7. Verilog-2005 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_ep_decode -- the first block a token reaches inside a device, and the
// two questions it has to answer before anything else can happen.
//
// EVERY DEVICE ON THE BUS SEES EVERY TOKEN
//
// USB is a broadcast medium. A hub repeats a downstream token to every
// enabled port, so a token addressed to one device is physically received by
// all of them. The only thing that stops six devices answering at once is
// that five of them look at the address field and say nothing.
//
// So this block is the device's answer to "is this mine?", and getting it
// wrong does not produce a slow device or a dropped transfer. It produces
// TWO DEVICES DRIVING THE SAME WIRE, which is a bus contention that corrupts
// the transaction for both of them and for everyone downstream.
//
// THE ADDRESS COMPARE IS AN EQUALITY, AND NOTHING ELSE
//
// A device responds to exactly one address: its own. There is no wildcard,
// no broadcast address, and no "also respond to 0 just in case".
//
// Address 0 feels like it should be special, and it is -- but not here. A
// device in the DEFAULT state HAS address 0 (chapter 21.5), so the plain
// equality already covers it. A device that has been assigned an address
// must NOT also answer address 0, because an unaddressed device on the same
// bus is using it, and the whole enumeration sequence depends on exactly one
// device holding address 0 at a time.
//
// THE ADDRESS IS SEVEN BITS
//
// Not eight, not six. A seven-bit compare done in six bits makes address 64
// indistinguishable from address 0 -- so a device at address 64 answers the
// enumeration traffic meant for whatever was just plugged in. That failure
// needs 65 devices on one bus before it can happen, which is why it survives
// bring-up and appears in the field.
//
// A NON-EXISTENT ENDPOINT GETS SILENCE
//
// The second question is "does the endpoint exist?", and the answer changes
// what the device does in a way that is easy to get backwards:
//
//   endpoint exists, halted    ->  STALL     (chapter 21.1)
//   endpoint does not exist    ->  NOTHING AT ALL
//
// A STALL is a statement about an endpoint: "this one is halted". Sending it
// for an endpoint that does not exist tells the host the endpoint DOES exist
// and is merely stalled -- so the host clears the halt and tries again, for
// ever. Silence lets the host's timeout conclude what is actually true.
//
// AND THE SELECT MUST BE ONE-HOT BY CONSTRUCTION
//
// Each endpoint is a block with its own state and its own FIFO port. They
// share a datapath, so two selects asserted at once is a contention INSIDE
// the device. The decode produces a one-hot-or-zero vector and nothing else,
// and that is a property worth asserting rather than assuming.
module usb_ep_decode #(
  parameter EP_N = 4,            // endpoints implemented
  parameter EPW  = 2             // width of the endpoint index
) (
  input  wire            clk,
  input  wire            rst_n,

  input  wire [6:0]      dev_address,   // OUR address (0 while in DEFAULT)
  input  wire            token_valid,
  input  wire [6:0]      token_addr,    // the address in the token packet
  input  wire [EPW-1:0]  token_ep,
  input  wire [EP_N-1:0] ep_enabled,    // which endpoints this device has

  output wire            for_us,        // the address matched
  output wire [EP_N-1:0] ep_select,     // ONE-HOT, or all zero
  output wire            ep_exists,
  output wire            unknown_ep,    // ours, but no such endpoint
  output wire            silent,        // say nothing at all
  output wire [1:0]      verdict,       // the same decision, named

  output reg [31:0]      n_tokens,
  output reg [31:0]      n_for_us,
  output reg [31:0]      n_not_ours,
  output reg [31:0]      n_unknown_ep,
  output reg [31:0]      n_selected
);
  // ---- QUESTION ONE: is this token addressed to us? ----
  //
  // A plain seven-bit equality. Every other formulation is a bug:
  //   (token_addr == dev_address) || (token_addr == 0)   -> collides with
  //                                                          an unaddressed
  //                                                          device
  //   (token_addr[5:0] == dev_address[5:0])              -> address 64
  //                                                          aliases to 0
  assign for_us = token_valid && (token_addr == dev_address);

  // ---- QUESTION TWO: does the endpoint exist? ----
  //
  // ep_enabled is a mask, one bit per implemented endpoint. An endpoint that
  // is not in it is not "disabled" -- it does not exist, and the device has
  // nothing to say about it.
  assign ep_exists = for_us && ep_enabled[token_ep];

  // ---- THE SELECT. One-hot or zero, by construction. ----
  //
  // Built by shifting a single bit, so two bits set is not a case the logic
  // can produce at all -- as opposed to a decoder built from comparisons,
  // where a mistake in one comparison produces contention rather than a
  // wrong answer.
  assign ep_select = ep_exists ? ({{(EP_N-1){1'b0}}, 1'b1} << token_ep)
                               : {EP_N{1'b0}};

  // Ours, but there is no such endpoint. The device says NOTHING -- a STALL
  // would assert that the endpoint exists and is halted.
  assign unknown_ep = for_us && !ep_enabled[token_ep];
  assign silent     = unknown_ep;

  // The same decision as one named value. Three of the four outcomes are
  // "the device drives nothing", and they are three different facts:
  // another device's business, ours-and-unanswerable, and no token at all.
  localparam [1:0] TOK_IDLE       = 2'd0,
                   TOK_NOT_OURS   = 2'd1,
                   TOK_NO_SUCH_EP = 2'd2,
                   TOK_SELECTED   = 2'd3;

  assign verdict = !token_valid ? TOK_IDLE
                 : !for_us      ? TOK_NOT_OURS
                 : unknown_ep   ? TOK_NO_SUCH_EP
                                : TOK_SELECTED;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_tokens     <= 32'd0;
      n_for_us     <= 32'd0;
      n_not_ours   <= 32'd0;
      n_unknown_ep <= 32'd0;
      n_selected   <= 32'd0;
    end else if (token_valid) begin
      n_tokens <= n_tokens + 32'd1;
      if (for_us)      n_for_us     <= n_for_us + 32'd1;
      else             n_not_ours   <= n_not_ours + 32'd1;
      if (unknown_ep)  n_unknown_ep <= n_unknown_ep + 32'd1;
      if (|ep_select)  n_selected   <= n_selected + 32'd1;
    end
  end
endmodule

8. SystemVerilog Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_ep_decode -- the first block a token reaches inside a device, and the
// two questions it has to answer before anything else can happen.
//
// EVERY DEVICE ON THE BUS SEES EVERY TOKEN
//
// USB is a broadcast medium. A hub repeats a downstream token to every
// enabled port, so a token addressed to one device is physically received by
// all of them. The only thing that stops six devices answering at once is
// that five of them look at the address field and say nothing.
//
// So this block is the device's answer to "is this mine?", and getting it
// wrong does not produce a slow device or a dropped transfer. It produces
// TWO DEVICES DRIVING THE SAME WIRE, which is a bus contention that corrupts
// the transaction for both of them and for everyone downstream.
//
// THE ADDRESS COMPARE IS AN EQUALITY, AND NOTHING ELSE
//
// A device responds to exactly one address: its own. There is no wildcard,
// no broadcast address, and no "also respond to 0 just in case".
//
// Address 0 feels like it should be special, and it is -- but not here. A
// device in the DEFAULT state HAS address 0 (chapter 21.5), so the plain
// equality already covers it. A device that has been assigned an address
// must NOT also answer address 0, because an unaddressed device on the same
// bus is using it, and the whole enumeration sequence depends on exactly one
// device holding address 0 at a time.
//
// THE ADDRESS IS SEVEN BITS
//
// Not eight, not six. A seven-bit compare done in six bits makes address 64
// indistinguishable from address 0 -- so a device at address 64 answers the
// enumeration traffic meant for whatever was just plugged in. That failure
// needs 65 devices on one bus before it can happen, which is why it survives
// bring-up and appears in the field.
//
// A NON-EXISTENT ENDPOINT GETS SILENCE
//
// The second question is "does the endpoint exist?", and the answer changes
// what the device does in a way that is easy to get backwards:
//
//   endpoint exists, halted    ->  STALL     (chapter 21.1)
//   endpoint does not exist    ->  NOTHING AT ALL
//
// A STALL is a statement about an endpoint: "this one is halted". Sending it
// for an endpoint that does not exist tells the host the endpoint DOES exist
// and is merely stalled -- so the host clears the halt and tries again, for
// ever. Silence lets the host's timeout conclude what is actually true.
//
// AND THE SELECT MUST BE ONE-HOT BY CONSTRUCTION
//
// Each endpoint is a block with its own state and its own FIFO port. They
// share a datapath, so two selects asserted at once is a contention INSIDE
// the device. The decode produces a one-hot-or-zero vector and nothing else,
// and that is a property worth asserting rather than assuming.
package usb_decode_pkg;
  // What the device decided about a token it saw. Every device on the bus
  // sees every token, so THREE of these mean "say nothing" and they are not
  // the same thing at all: NOT_OURS is another device's business, NO_SUCH_EP
  // is ours and unanswerable, and IDLE is no token at all.
  typedef enum logic [1:0] {
    TOK_IDLE       = 2'd0,   // no token present
    TOK_NOT_OURS   = 2'd1,   // addressed to another device
    TOK_NO_SUCH_EP = 2'd2,   // ours, but no such endpoint: SILENCE
    TOK_SELECTED   = 2'd3    // ours, and the endpoint exists
  } token_verdict_e;
endpackage

module usb_ep_decode
  import usb_decode_pkg::*;
#(
  parameter int EP_N = 4,        // endpoints implemented
  parameter int EPW  = 2         // width of the endpoint index
) (
  input  logic            clk,
  input  logic            rst_n,

  input  logic [6:0]      dev_address,  // OUR address (0 while in DEFAULT)
  input  logic            token_valid,
  input  logic [6:0]      token_addr,   // the address in the token packet
  input  logic [EPW-1:0]  token_ep,
  input  logic [EP_N-1:0] ep_enabled,   // which endpoints this device has

  output logic            for_us,       // the address matched
  output logic [EP_N-1:0] ep_select,    // ONE-HOT, or all zero
  output logic            ep_exists,
  output logic            unknown_ep,   // ours, but no such endpoint
  output logic            silent,       // say nothing at all
  output token_verdict_e  verdict,      // the same decision, named

  output logic [31:0]     n_tokens,
  output logic [31:0]     n_for_us,
  output logic [31:0]     n_not_ours,
  output logic [31:0]     n_unknown_ep,
  output logic [31:0]     n_selected
);
  // ---- QUESTION ONE: is this token addressed to us? ----
  //
  // A plain seven-bit equality. Every other formulation is a bug:
  //   (token_addr == dev_address) || (token_addr == 0)   -> collides with
  //                                                          an unaddressed
  //                                                          device
  //   (token_addr[5:0] == dev_address[5:0])              -> address 64
  //                                                          aliases to 0
  assign for_us = token_valid && (token_addr == dev_address);

  // ---- QUESTION TWO: does the endpoint exist? ----
  //
  // ep_enabled is a mask, one bit per implemented endpoint. An endpoint that
  // is not in it is not "disabled" -- it does not exist, and the device has
  // nothing to say about it.
  assign ep_exists = for_us && ep_enabled[token_ep];

  // ---- THE SELECT. One-hot or zero, by construction. ----
  //
  // Built by shifting a single bit, so two bits set is not a case the logic
  // can produce at all -- as opposed to a decoder built from comparisons,
  // where a mistake in one comparison produces contention rather than a
  // wrong answer.
  assign ep_select = ep_exists ? (EP_N'(1) << token_ep) : '0;

  // Ours, but there is no such endpoint. The device says NOTHING -- a STALL
  // would assert that the endpoint exists and is halted.
  assign unknown_ep = for_us && !ep_enabled[token_ep];
  assign silent     = unknown_ep;

  // The same decision as one named value. Three of the four outcomes are
  // "the device drives nothing", and they are three different facts.
  always_comb begin
    if      (!token_valid) verdict = TOK_IDLE;
    else if (!for_us)      verdict = TOK_NOT_OURS;
    else if (unknown_ep)   verdict = TOK_NO_SUCH_EP;
    else                   verdict = TOK_SELECTED;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_tokens     <= '0;
      n_for_us     <= '0;
      n_not_ours   <= '0;
      n_unknown_ep <= '0;
      n_selected   <= '0;
    end else if (token_valid) begin
      n_tokens <= n_tokens + 1;
      if (for_us)      n_for_us     <= n_for_us + 1;
      else             n_not_ours   <= n_not_ours + 1;
      if (unknown_ep)  n_unknown_ep <= n_unknown_ep + 1;
      if (|ep_select)  n_selected   <= n_selected + 1;
    end
  end
endmodule

9. VHDL-2008 Implementation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- usb_ep_decode -- the first block a token reaches inside a device, and the
-- two questions it has to answer before anything else can happen.
--
-- EVERY DEVICE ON THE BUS SEES EVERY TOKEN
--
-- USB is a broadcast medium. A hub repeats a downstream token to every
-- enabled port, so a token addressed to one device is physically received by
-- all of them. The only thing that stops six devices answering at once is
-- that five of them look at the address field and say nothing.
--
-- So this block is the device's answer to "is this mine?", and getting it
-- wrong does not produce a slow device or a dropped transfer. It produces
-- TWO DEVICES DRIVING THE SAME WIRE, which corrupts the transaction for both
-- of them and for everyone downstream.
--
-- THE ADDRESS COMPARE IS AN EQUALITY, AND NOTHING ELSE
--
-- A device responds to exactly one address: its own. There is no wildcard,
-- no broadcast address, and no "also respond to 0 just in case".
--
-- Address 0 feels like it should be special, and it is -- but not here. A
-- device in the DEFAULT state HAS address 0 (chapter 21.5), so the plain
-- equality already covers it. A device that has been assigned an address must
-- NOT also answer address 0, because an unaddressed device on the same bus is
-- using it, and the whole enumeration sequence depends on exactly one device
-- holding address 0 at a time.
--
-- THE ADDRESS IS SEVEN BITS
--
-- Not eight, not six. A seven-bit compare done in six bits makes address 64
-- indistinguishable from address 0 -- so a device at address 64 answers the
-- enumeration traffic meant for whatever was just plugged in. That failure
-- needs 65 devices on one bus before it can happen, which is why it survives
-- bring-up and appears in the field.
--
-- A NON-EXISTENT ENDPOINT GETS SILENCE
--
--   endpoint exists, halted    ->  STALL     (chapter 21.1)
--   endpoint does not exist    ->  NOTHING AT ALL
--
-- A STALL is a statement about an endpoint: "this one is halted". Sending it
-- for an endpoint that does not exist tells the host the endpoint DOES exist
-- and is merely stalled -- so the host clears the halt and tries again, for
-- ever. Silence lets the host's timeout conclude what is actually true.
--
-- AND THE SELECT MUST BE ONE-HOT BY CONSTRUCTION
--
-- Each endpoint is a block with its own state and its own FIFO port. They
-- share a datapath, so two selects asserted at once is a contention INSIDE
-- the device. The decode produces a one-hot-or-zero vector and nothing else.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb_decode_pkg is
  -- What the device decided about a token it saw. THREE of these mean "say
  -- nothing" and they are not the same thing at all: TOK_NOT_OURS is another
  -- device's business, TOK_NO_SUCH_EP is ours and unanswerable, and TOK_IDLE
  -- is no token at all.
  type token_verdict_t is (
    TOK_IDLE,        -- no token present
    TOK_NOT_OURS,    -- addressed to another device
    TOK_NO_SUCH_EP,  -- ours, but no such endpoint: SILENCE
    TOK_SELECTED     -- ours, and the endpoint exists
  );

  function verdict_code(v : token_verdict_t) return std_logic_vector;
end package;

package body usb_decode_pkg is
  function verdict_code(v : token_verdict_t) return std_logic_vector is
  begin
    return std_logic_vector(to_unsigned(token_verdict_t'pos(v), 2));
  end function;
end package body;

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

entity usb_ep_decode is
  generic (
    EP_N : natural := 4;          -- endpoints implemented
    EPW  : natural := 2           -- width of the endpoint index
  );
  port (
    clk          : in  std_logic;
    rst_n        : in  std_logic;

    dev_address  : in  std_logic_vector(6 downto 0);  -- OUR address
    token_valid  : in  std_logic;
    token_addr   : in  std_logic_vector(6 downto 0);
    token_ep     : in  std_logic_vector(EPW-1 downto 0);
    ep_enabled   : in  std_logic_vector(EP_N-1 downto 0);

    for_us       : out std_logic;
    ep_select    : out std_logic_vector(EP_N-1 downto 0);  -- ONE-HOT or zero
    ep_exists    : out std_logic;
    unknown_ep   : out std_logic;
    silent       : out std_logic;
    verdict      : out std_logic_vector(1 downto 0);

    n_tokens     : out std_logic_vector(31 downto 0);
    n_for_us     : out std_logic_vector(31 downto 0);
    n_not_ours   : out std_logic_vector(31 downto 0);
    n_unknown_ep : out std_logic_vector(31 downto 0);
    n_selected   : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of usb_ep_decode is
  signal us_s, exists_s, unk_s : std_logic;
  signal sel_s : std_logic_vector(EP_N-1 downto 0);
  signal vd_s  : token_verdict_t;

  signal tok_c, us_c, no_c, unk_c, sel_c : unsigned(31 downto 0)
       := (others => '0');
begin
  -- ---- QUESTION ONE: is this token addressed to us? ----
  --
  -- A plain seven-bit equality. Every other formulation is a bug:
  --   token_addr = dev_address or token_addr = 0  -> collides with an
  --                                                  unaddressed device
  --   token_addr(5 downto 0) = dev_address(5 downto 0)
  --                                               -> address 64 aliases to 0
  us_s <= '1' when (token_valid = '1' and token_addr = dev_address) else '0';

  -- ---- QUESTION TWO: does the endpoint exist? ----
  --
  -- ep_enabled is a mask, one bit per implemented endpoint. An endpoint not
  -- in it is not "disabled" -- it does not exist, and the device has nothing
  -- to say about it.
  exists_s <= '1' when (us_s = '1'
                        and ep_enabled(to_integer(unsigned(token_ep))) = '1')
              else '0';

  -- ---- THE SELECT. One-hot or zero, by construction. ----
  --
  -- Built by shifting a single bit, so two bits set is not a case the logic
  -- can produce at all -- as opposed to a decoder built from comparisons,
  -- where a mistake in one comparison produces contention rather than a
  -- wrong answer.
  onehot : process (exists_s, token_ep)
    variable v : std_logic_vector(EP_N-1 downto 0);
  begin
    v := (others => '0');
    if exists_s = '1' then
      v(to_integer(unsigned(token_ep))) := '1';
    end if;
    sel_s <= v;
  end process;

  -- Ours, but there is no such endpoint. The device says NOTHING -- a STALL
  -- would assert that the endpoint exists and is halted.
  unk_s <= '1' when (us_s = '1'
                     and ep_enabled(to_integer(unsigned(token_ep))) = '0')
           else '0';

  for_us     <= us_s;
  ep_exists  <= exists_s;
  ep_select  <= sel_s;
  unknown_ep <= unk_s;
  silent     <= unk_s;

  -- The same decision as one named value. Three of the four outcomes are
  -- "the device drives nothing", and they are three different facts.
  classify : process (token_valid, us_s, unk_s)
  begin
    if token_valid = '0' then
      vd_s <= TOK_IDLE;
    elsif us_s = '0' then
      vd_s <= TOK_NOT_OURS;
    elsif unk_s = '1' then
      vd_s <= TOK_NO_SUCH_EP;
    else
      vd_s <= TOK_SELECTED;
    end if;
  end process;

  verdict <= verdict_code(vd_s);

  regs : process (clk, rst_n)
  begin
    if rst_n = '0' then
      tok_c <= (others => '0');
      us_c  <= (others => '0');
      no_c  <= (others => '0');
      unk_c <= (others => '0');
      sel_c <= (others => '0');
    elsif rising_edge(clk) then
      if token_valid = '1' then
        tok_c <= tok_c + 1;
        if us_s = '1' then
          us_c <= us_c + 1;
        else
          no_c <= no_c + 1;
        end if;
        if unk_s = '1' then
          unk_c <= unk_c + 1;
        end if;
        if sel_s /= (sel_s'range => '0') then
          sel_c <= sel_c + 1;
        end if;
      end if;
    end if;
  end process;

  n_tokens     <= std_logic_vector(tok_c);
  n_for_us     <= std_logic_vector(us_c);
  n_not_ours   <= std_logic_vector(no_c);
  n_unknown_ep <= std_logic_vector(unk_c);
  n_selected   <= std_logic_vector(sel_c);
end architecture;

9.1 Seeing the four verdicts

Four tokens on the wire, and four different silences

usb_ep_decode — one bus, four devices, one answer

10 cycles
A ten-cycle waveform. The device address is 9 with endpoints 0, 1 and 3 enabled. At cycle 1 a token for address 9 endpoint 1 selects endpoint 1. At cycle 3 a token for address 9 endpoint 2 produces silence because that endpoint does not exist. At cycle 5 a token for address 10 is ignored entirely. At cycle 7 a token for address 0 is ignored because this device is addressed.ours, endpoint existsours, endpoint existsours, no such endpointours, no such endpointanother device's tokenanother device's tokenaddress 0: being enumeratedaddress 0: being enumeratedclktoken_validtoken_addr099991010000token_ep0112211000for_usep_select0000001000000000000000000000000000000000silentverdictIDLESELECTEDIDLENO_SUCH_EPIDLENOT_OURSIDLENOT_OURSIDLEIDLEt0t1t2t3t4t5t6t7t8t9
This device is at address 9 with endpoints 0, 1 and 3. Cycle 1 selects endpoint 1. Cycle 3 is ours but names endpoint 2, which does not exist — silence. Cycle 5 is for address 10 — also silence, but not ours to produce. Cycle 7 is address 0, which belongs to whatever device the host is enumerating.

ep_select is 0000 on seven of the eight token cycles, and the row below it is the only thing that says why. Cycles 3, 5 and 7 are three different silences — one of them ours to produce, two of them not.

10. The Testbenches

Two exhaustive domains, because the address compare and the endpoint decode are independent questions and one of them has a very large domain:

DomainWhat it enumeratesSize
A — address compareevery device address × every token address16384
B — endpoint decode16 enable masks × 4 endpoints × address match/mismatch × token valid/not256

10.1 Verilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module tb_dc_v;
  localparam EP_N = 4, EPW = 2;
  reg clk=0, rst_n=0;
  reg token_valid=0;
  reg [6:0] dev_address=0, token_addr=0;
  reg [EPW-1:0] token_ep=0;
  reg [EP_N-1:0] ep_enabled=0;
  wire for_us, ep_exists, unknown_ep, silent;
  wire [EP_N-1:0] ep_select;
  wire [1:0] verdict;
  wire [31:0] n_tokens, n_for_us, n_not_ours, n_unknown_ep, n_selected;
  always #5 clk=~clk;

  usb_ep_decode #(.EP_N(EP_N), .EPW(EPW)) dut (
    .clk(clk), .rst_n(rst_n), .dev_address(dev_address),
    .token_valid(token_valid), .token_addr(token_addr), .token_ep(token_ep),
    .ep_enabled(ep_enabled), .for_us(for_us), .ep_select(ep_select),
    .ep_exists(ep_exists), .unknown_ep(unknown_ep), .silent(silent),
    .verdict(verdict),
    .n_tokens(n_tokens), .n_for_us(n_for_us), .n_not_ours(n_not_ours),
    .n_unknown_ep(n_unknown_ep), .n_selected(n_selected));

  integer errors=0, i, j, k, m, v;
  integer n_addr_exh=0, n_ep_exh=0;
  integer n_match=0, n_nomatch=0, n_sel=0, n_unk=0;
  integer n_perep [0:3];
  integer m_tok, m_us, m_no, m_unk, m_sel;

  // count the set bits of a vector -- used for the one-hot property
  function integer popcount;
    input [EP_N-1:0] v;
    integer b, c;
    begin
      c = 0;
      for (b=0; b<EP_N; b=b+1) if (v[b]) c = c + 1;
      popcount = c;
    end
  endfunction

  task check(input cond, input [639:0] msg);
    begin if (!cond) begin errors=errors+1;
      if (errors <= 25)
        $display("  FAIL: %0s (vld=%b dev=%0d tok=%0d ep=%0d en=%b | for_us=%b exists=%b sel=%b unk=%b silent=%b, t=%0t)",
                 msg, token_valid, dev_address, token_addr, token_ep,
                 ep_enabled, for_us, ep_exists, ep_select, unknown_ep,
                 silent, $time);
    end end
  endtask

  localparam [1:0] TOK_IDLE=0, TOK_NOT_OURS=1, TOK_NO_SUCH_EP=2,
                   TOK_SELECTED=3;

  task check_comb;
    reg e_for_us, e_exists, e_unk;
    reg [EP_N-1:0] e_sel;
    reg [1:0] e_verd;
    integer b;
    begin
      // The model builds the one-hot select by a LOOP over the endpoints
      // where the design shifts a single bit -- a different route, and one
      // that would happily produce two bits set if the rule allowed it.
      e_for_us = token_valid && (token_addr == dev_address);
      e_exists = e_for_us && ep_enabled[token_ep];
      e_unk    = e_for_us && !ep_enabled[token_ep];
      e_sel = {EP_N{1'b0}};
      for (b=0; b<EP_N; b=b+1)
        if (e_exists && (token_ep == b[EPW-1:0])) e_sel[b] = 1'b1;

      check(for_us     === e_for_us, "for_us matches the model");
      check(ep_exists  === e_exists, "ep_exists matches the model");
      check(ep_select  === e_sel,    "ep_select matches the model");
      check(unknown_ep === e_unk,    "unknown_ep matches the model");
      check(silent     === e_unk,    "silent matches unknown_ep");

      if      (!token_valid) e_verd = TOK_IDLE;
      else if (!e_for_us)    e_verd = TOK_NOT_OURS;
      else if (e_unk)        e_verd = TOK_NO_SUCH_EP;
      else                   e_verd = TOK_SELECTED;
      check(verdict === e_verd, "verdict matches the model");
      // The named verdict must agree with the signals it summarises.
      check((verdict === TOK_SELECTED) === (|ep_select),
            "verdict disagrees with ep_select");
      check((verdict === TOK_NO_SUCH_EP) === silent,
            "verdict disagrees with silent");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE property. The select is ONE-HOT or zero. Two endpoint blocks
      //    driving the shared datapath is a contention inside the device.
      check(popcount(ep_select) <= 1,
            "more than one endpoint was selected -- that is a bus contention inside the device");
      // 2. A token for another device selects nothing and says nothing.
      if (token_valid && (token_addr !== dev_address)) begin
        check(!for_us, "a token for another address was claimed");
        check(ep_select === {EP_N{1'b0}},
              "a token for another device selected an endpoint");
        check(!silent,
              "a token for another device produced a silence WE are responsible for");
      end
      // 3. Address 0 is not a wildcard. An ADDRESSED device must not answer
      //    it -- an unaddressed device on the same bus is using it.
      if (token_valid && (token_addr === 7'd0) && (dev_address !== 7'd0))
        check(!for_us,
              "an addressed device answered address 0 -- it will collide with the device being enumerated");
      // 4. The address is SEVEN bits. Address 64 must not alias to 0.
      if (token_valid && (token_addr === 7'd64) && (dev_address === 7'd0))
        check(!for_us,
              "address 64 matched address 0 -- the compare is only six bits wide");
      if (token_valid && (token_addr === 7'd0) && (dev_address === 7'd64))
        check(!for_us, "address 0 matched address 64");
      // 5. A non-existent endpoint gets SILENCE, and selects nothing.
      if (unknown_ep) begin
        check(ep_select === {EP_N{1'b0}},
              "a non-existent endpoint was selected");
        check(silent,
              "a non-existent endpoint did not produce silence -- a STALL would claim it exists");
      end
      // 6. Selecting and not-existing are mutually exclusive.
      check(!(|ep_select && unknown_ep),
            "an endpoint was both selected and unknown");
      // 7. Nothing at all happens without a token.
      if (!token_valid)
        check(!for_us && !silent && (ep_select === {EP_N{1'b0}}),
              "the decode responded with no token present");
      // 8. A selected endpoint is the one the token named.
      if (|ep_select)
        check(ep_select[token_ep],
              "the selected endpoint is not the one the token addressed");

      if (e_for_us) n_match = n_match + 1; else if (token_valid) n_nomatch = n_nomatch + 1;
      if (|e_sel)   begin n_sel = n_sel + 1; n_perep[token_ep] = n_perep[token_ep] + 1; end
      if (e_unk)    n_unk = n_unk + 1;
    end
  endtask

  task step;
    begin
      #1;
      check_comb;
      if (token_valid) begin
        m_tok = m_tok + 1;
        if (for_us) m_us = m_us + 1; else m_no = m_no + 1;
        if (unknown_ep) m_unk = m_unk + 1;
        if (|ep_select) m_sel = m_sel + 1;
      end
      @(posedge clk); #1;
      check(n_tokens     === m_tok[31:0], "n_tokens matches the model");
      check(n_for_us     === m_us[31:0],  "n_for_us matches the model");
      check(n_not_ours   === m_no[31:0],  "n_not_ours matches the model");
      check(n_unknown_ep === m_unk[31:0], "n_unknown_ep matches the model");
      check(n_selected   === m_sel[31:0], "n_selected matches the model");
    end
  endtask

  task hard_reset;
    begin
      rst_n=0; token_valid=0; dev_address=0; token_addr=0; token_ep=0;
      ep_enabled=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      m_tok=0; m_us=0; m_no=0; m_unk=0; m_sel=0;
    end
  endtask

  initial begin
    for (i=0;i<4;i=i+1) n_perep[i]=0;
    hard_reset;
    check(!for_us, "no token, no match");
    check(ep_select === 4'b0000, "and nothing selected");

    // ===== A. EXHAUSTIVE address-compare sweep =====
    // Every one of the 128 device addresses against every one of the 128
    // token addresses = 16384 points. That is the ENTIRE address domain,
    // which is what it takes to prove the compare is seven bits wide and
    // has no wildcard anywhere in it.
    token_valid=1; ep_enabled=4'b1111; token_ep=2'd0;
    for (i=0; i<128; i=i+1)
     for (j=0; j<128; j=j+1) begin
       dev_address = i[6:0]; token_addr = j[6:0];
       step;
       n_addr_exh = n_addr_exh + 1;
     end
    token_valid=0;
    $display("  exhaustive address-compare sweep: %0d of 16384 pairs verified",
             n_addr_exh);

    // ===== B. EXHAUSTIVE endpoint-decode sweep =====
    // Every endpoint-enable mask against every endpoint index, with the
    // address matching and not matching, and with and without a token:
    //   16 masks x 4 endpoints x 2 (address match) x 2 (token_valid) = 256
    for (m=0; m<16; m=m+1)
     for (k=0; k<4; k=k+1)
      for (i=0; i<2; i=i+1)
       for (v=0; v<2; v=v+1) begin
         dev_address = 7'd37;
         token_addr  = i[0] ? 7'd37 : 7'd38;
         token_ep    = k[EPW-1:0];
         ep_enabled  = m[EP_N-1:0];
         token_valid = v[0];
         step;
         n_ep_exh = n_ep_exh + 1;
       end
    token_valid=0;
    $display("  exhaustive endpoint-decode sweep: %0d of 256 points verified",
             n_ep_exh);

    // ===== C. directed: the cases that cause bus contention =====
    hard_reset;
    dev_address = 7'd9; ep_enabled = 4'b1011;

    // 1. Our address, an endpoint we have.
    token_valid=1; token_addr=7'd9; token_ep=2'd1; #1;
    check(for_us, "a token for our address is ours");
    check(ep_exists, "endpoint 1 exists");
    check(ep_select === 4'b0010, "and exactly endpoint 1 is selected");
    step;

    // 2. Our address, an endpoint we do NOT have.
    token_valid=1; token_addr=7'd9; token_ep=2'd2; #1;
    check(for_us, "still our address");
    check(!ep_exists, "but endpoint 2 does not exist on this device");
    check(ep_select === 4'b0000, "so nothing is selected");
    check(silent,
          "and the device says NOTHING -- a STALL would claim the endpoint exists");
    step;

    // 3. Somebody else's address.
    token_valid=1; token_addr=7'd10; token_ep=2'd1; #1;
    check(!for_us, "a token for address 10 is not ours");
    check(ep_select === 4'b0000, "nothing selected");
    check(!silent, "and the silence is not ours to produce");
    step;

    // 4. THE enumeration hazard. We are addressed; a device being enumerated
    //    is at address 0. We must not answer its tokens.
    token_valid=1; token_addr=7'd0; token_ep=2'd0; #1;
    check(!for_us,
          "an ADDRESSED device must not answer address 0 -- the device being enumerated is using it");
    check(ep_select === 4'b0000, "so nothing is selected");
    step;

    // 5. ...and while WE are unaddressed, address 0 IS ours.
    dev_address = 7'd0;
    token_valid=1; token_addr=7'd0; token_ep=2'd0; #1;
    check(for_us,
          "a device in the DEFAULT state HAS address 0, so the plain equality covers it");
    check(ep_select === 4'b0001, "and endpoint 0 is selected");
    step;

    // 6. THE seven-bit hazard. Address 64 differs from address 0 in bit 6
    //    alone. A six-bit compare makes them the same device.
    dev_address = 7'd0;
    token_valid=1; token_addr=7'd64; token_ep=2'd0; #1;
    check(!for_us,
          "address 64 is NOT address 0 -- the compare is seven bits wide");
    step;
    dev_address = 7'd64;
    token_valid=1; token_addr=7'd0; token_ep=2'd0; #1;
    check(!for_us, "and address 0 is not address 64");
    step;

    // 7. The highest address, exactly.
    dev_address = 7'd127;
    token_valid=1; token_addr=7'd127; token_ep=2'd0; #1;
    check(for_us, "address 127 matches itself");
    step;
    token_valid=1; token_addr=7'd126; token_ep=2'd0; #1;
    check(!for_us, "and does not match 126");
    step;
    token_valid=0;

    // ===== D. randomised =====
    hard_reset;
    for (i=0;i<40000;i=i+1) begin
      dev_address = {$random}%128;
      // bias hard toward a matching address -- a random 7-bit pair matches
      // once in 128, and the interesting half of this block is downstream
      // of a match
      token_addr  = (({$random}%3)!=0) ? dev_address : ({$random}%128);
      token_ep    = {$random}%4;
      ep_enabled  = {$random}%16;
      token_valid = ({$random}%8)!=0;
      step;
    end

    for (i=0;i<4;i=i+1)
      check(n_perep[i] > 500, "every endpoint was selected many times");
    check(n_match   > 5000, "address matches happened often");
    check(n_nomatch > 5000, "address mismatches happened often");
    check(n_unk     > 2000, "unknown endpoints were addressed often");

    $display("");
    $display("  REACH: address-pairs=%0d ep-points=%0d | matched=%0d not-ours=%0d selected=%0d unknown-ep=%0d",
             n_addr_exh, n_ep_exh, n_match, n_nomatch, n_sel, n_unk);
    $display("  PER-ENDPOINT selects: ep0=%0d ep1=%0d ep2=%0d ep3=%0d",
             n_perep[0], n_perep[1], n_perep[2], n_perep[3]);
    $display("  COUNTERS: tokens=%0d for-us=%0d not-ours=%0d unknown-ep=%0d selected=%0d",
             n_tokens, n_for_us, n_not_ours, n_unknown_ep, n_selected);
    $display("  [Verilog] usb_ep_decode: %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_dc_sv;
  import usb_decode_pkg::*;

  localparam EP_N = 4, EPW = 2;
  logic clk=0, rst_n=0;
  logic token_valid=0;
  logic [6:0] dev_address=0, token_addr=0;
  logic [EPW-1:0] token_ep=0;
  logic [EP_N-1:0] ep_enabled=0;
  logic for_us, ep_exists, unknown_ep, silent;
  logic [EP_N-1:0] ep_select;
  token_verdict_e verdict;
  logic [31:0] n_tokens, n_for_us, n_not_ours, n_unknown_ep, n_selected;

  // 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 = 23101;
  always #5 clk=~clk;

  usb_ep_decode #(.EP_N(EP_N), .EPW(EPW)) dut (
    .clk, .rst_n, .dev_address, .token_valid, .token_addr, .token_ep,
    .ep_enabled, .for_us, .ep_select, .ep_exists, .unknown_ep, .silent,
    .verdict, .n_tokens, .n_for_us, .n_not_ours, .n_unknown_ep,
    .n_selected);

  int errors=0, i, j, k, m, v;
  int n_addr_exh=0, n_ep_exh=0;
  int n_match=0, n_nomatch=0, n_sel=0, n_unk=0;
  int n_perep [4];
  int m_tok, m_us, m_no, m_unk, m_sel;

  // count the set bits of a vector -- used for the one-hot property
  function automatic int popcount(input logic [EP_N-1:0] vec);
    int c = 0;
    for (int b = 0; b < EP_N; b++) if (vec[b]) c++;
    return c;
  endfunction

  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.
    token_verdict_e vd_v;
    if (!cond) begin
      errors++;
      vd_v = verdict;
      if (errors <= 25)
        $display("  FAIL: %0s (vld=%b dev=%0d tok=%0d ep=%0d en=%b | for_us=%b exists=%b sel=%b verdict=%s, t=%0t)",
                 msg, token_valid, dev_address, token_addr, token_ep,
                 ep_enabled, for_us, ep_exists, ep_select, vd_v.name(),
                 $time);
    end
  endtask

  task automatic check_comb;
    bit e_for_us, e_exists, e_unk;
    logic [EP_N-1:0] e_sel;
    token_verdict_e e_verd;
    int b;
    begin
      // The model builds the one-hot select by a LOOP over the endpoints
      // where the design shifts a single bit -- a different route, and one
      // that would happily produce two bits set if the rule allowed it.
      e_for_us = token_valid && (token_addr == dev_address);
      e_exists = e_for_us && ep_enabled[token_ep];
      e_unk    = e_for_us && !ep_enabled[token_ep];
      e_sel = '0;
      for (b=0; b<EP_N; b++)
        if (e_exists && (token_ep == EPW'(b))) e_sel[b] = 1'b1;

      check(for_us     === e_for_us, "for_us matches the model");
      check(ep_exists  === e_exists, "ep_exists matches the model");
      check(ep_select  === e_sel,    "ep_select matches the model");
      check(unknown_ep === e_unk,    "unknown_ep matches the model");
      check(silent     === e_unk,    "silent matches unknown_ep");

      if      (!token_valid) e_verd = TOK_IDLE;
      else if (!e_for_us)    e_verd = TOK_NOT_OURS;
      else if (e_unk)        e_verd = TOK_NO_SUCH_EP;
      else                   e_verd = TOK_SELECTED;
      check(verdict === e_verd, "verdict matches the model");
      // The named verdict must agree with the signals it summarises.
      check((verdict === TOK_SELECTED) === (|ep_select),
            "verdict disagrees with ep_select");
      check((verdict === TOK_NO_SUCH_EP) === silent,
            "verdict disagrees with silent");

      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE property. The select is ONE-HOT or zero. Two endpoint blocks
      //    driving the shared datapath is a contention inside the device.
      check(popcount(ep_select) <= 1,
            "more than one endpoint was selected -- that is a bus contention inside the device");
      // 2. A token for another device selects nothing and says nothing.
      if (token_valid && (token_addr !== dev_address)) begin
        check(!for_us, "a token for another address was claimed");
        check(ep_select === '0,
              "a token for another device selected an endpoint");
        check(!silent,
              "a token for another device produced a silence WE are responsible for");
      end
      // 3. Address 0 is not a wildcard. An ADDRESSED device must not answer
      //    it -- an unaddressed device on the same bus is using it.
      if (token_valid && (token_addr === 7'd0) && (dev_address !== 7'd0))
        check(!for_us,
              "an addressed device answered address 0 -- it will collide with the device being enumerated");
      // 4. The address is SEVEN bits. Address 64 must not alias to 0.
      if (token_valid && (token_addr === 7'd64) && (dev_address === 7'd0))
        check(!for_us,
              "address 64 matched address 0 -- the compare is only six bits wide");
      if (token_valid && (token_addr === 7'd0) && (dev_address === 7'd64))
        check(!for_us, "address 0 matched address 64");
      // 5. A non-existent endpoint gets SILENCE, and selects nothing.
      if (unknown_ep) begin
        check(ep_select === '0,
              "a non-existent endpoint was selected");
        check(silent,
              "a non-existent endpoint did not produce silence -- a STALL would claim it exists");
      end
      // 6. Selecting and not-existing are mutually exclusive.
      check(!(|ep_select && unknown_ep),
            "an endpoint was both selected and unknown");
      // 7. Nothing at all happens without a token.
      if (!token_valid)
        check(!for_us && !silent && (ep_select === '0),
              "the decode responded with no token present");
      // 8. A selected endpoint is the one the token named.
      if (|ep_select)
        check(ep_select[token_ep],
              "the selected endpoint is not the one the token addressed");

      if (e_for_us) n_match = n_match + 1; else if (token_valid) n_nomatch = n_nomatch + 1;
      if (|e_sel)   begin n_sel++; n_perep[int'(token_ep)]++; end
      if (e_unk)    n_unk = n_unk + 1;
    end
  endtask

  task automatic step;
    begin
      #1;
      check_comb;
      if (token_valid) begin
        m_tok = m_tok + 1;
        if (for_us) m_us = m_us + 1; else m_no = m_no + 1;
        if (unknown_ep) m_unk = m_unk + 1;
        if (|ep_select) m_sel = m_sel + 1;
      end
      @(posedge clk); #1;
      check(n_tokens     === 32'(m_tok), "n_tokens matches the model");
      check(n_for_us     === 32'(m_us),  "n_for_us matches the model");
      check(n_not_ours   === 32'(m_no),  "n_not_ours matches the model");
      check(n_unknown_ep === 32'(m_unk), "n_unknown_ep matches the model");
      check(n_selected   === 32'(m_sel), "n_selected matches the model");
    end
  endtask

  task automatic hard_reset;
    begin
      rst_n=0; token_valid=0; dev_address=0; token_addr=0; token_ep=0;
      ep_enabled=0;
      @(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
      m_tok=0; m_us=0; m_no=0; m_unk=0; m_sel=0;
    end
  endtask

  initial begin
    void'($urandom(urandom_seed));
    foreach (n_perep[i]) n_perep[i]=0;
    hard_reset;
    check(!for_us, "no token, no match");
    check(ep_select === '0, "and nothing selected");

    // ===== A. EXHAUSTIVE address-compare sweep =====
    // Every one of the 128 device addresses against every one of the 128
    // token addresses = 16384 points. That is the ENTIRE address domain,
    // which is what it takes to prove the compare is seven bits wide and
    // has no wildcard anywhere in it.
    token_valid=1; ep_enabled=4'b1111; token_ep=2'd0;
    for (i=0; i<128; i=i+1)
     for (j=0; j<128; j=j+1) begin
       dev_address = 7'(i); token_addr = 7'(j);
       step;
       n_addr_exh = n_addr_exh + 1;
     end
    token_valid=0;
    $display("  exhaustive address-compare sweep: %0d of 16384 pairs verified",
             n_addr_exh);

    // ===== B. EXHAUSTIVE endpoint-decode sweep =====
    // Every endpoint-enable mask against every endpoint index, with the
    // address matching and not matching, and with and without a token:
    //   16 masks x 4 endpoints x 2 (address match) x 2 (token_valid) = 256
    for (m=0; m<16; m=m+1)
     for (k=0; k<4; k=k+1)
      for (i=0; i<2; i=i+1)
       for (v=0; v<2; v=v+1) begin
         dev_address = 7'd37;
         token_addr  = i[0] ? 7'd37 : 7'd38;
         token_ep    = EPW'(k);
         ep_enabled  = EP_N'(m);
         token_valid = v[0];
         step;
         n_ep_exh = n_ep_exh + 1;
       end
    token_valid=0;
    $display("  exhaustive endpoint-decode sweep: %0d of 256 points verified",
             n_ep_exh);

    // ===== C. directed: the cases that cause bus contention =====
    hard_reset;
    dev_address = 7'd9; ep_enabled = 4'b1011;

    // 1. Our address, an endpoint we have.
    token_valid=1; token_addr=7'd9; token_ep=2'd1; #1;
    check(for_us, "a token for our address is ours");
    check(ep_exists, "endpoint 1 exists");
    check(ep_select === 4'b0010, "and exactly endpoint 1 is selected");
    step;

    // 2. Our address, an endpoint we do NOT have.
    token_valid=1; token_addr=7'd9; token_ep=2'd2; #1;
    check(for_us, "still our address");
    check(!ep_exists, "but endpoint 2 does not exist on this device");
    check(ep_select === '0, "so nothing is selected");
    check(silent,
          "and the device says NOTHING -- a STALL would claim the endpoint exists");
    step;

    // 3. Somebody else's address.
    token_valid=1; token_addr=7'd10; token_ep=2'd1; #1;
    check(!for_us, "a token for address 10 is not ours");
    check(ep_select === '0, "nothing selected");
    check(!silent, "and the silence is not ours to produce");
    step;

    // 4. THE enumeration hazard. We are addressed; a device being enumerated
    //    is at address 0. We must not answer its tokens.
    token_valid=1; token_addr=7'd0; token_ep=2'd0; #1;
    check(!for_us,
          "an ADDRESSED device must not answer address 0 -- the device being enumerated is using it");
    check(ep_select === '0, "so nothing is selected");
    step;

    // 5. ...and while WE are unaddressed, address 0 IS ours.
    dev_address = 7'd0;
    token_valid=1; token_addr=7'd0; token_ep=2'd0; #1;
    check(for_us,
          "a device in the DEFAULT state HAS address 0, so the plain equality covers it");
    check(ep_select === 4'b0001, "and endpoint 0 is selected");
    step;

    // 6. THE seven-bit hazard. Address 64 differs from address 0 in bit 6
    //    alone. A six-bit compare makes them the same device.
    dev_address = 7'd0;
    token_valid=1; token_addr=7'd64; token_ep=2'd0; #1;
    check(!for_us,
          "address 64 is NOT address 0 -- the compare is seven bits wide");
    step;
    dev_address = 7'd64;
    token_valid=1; token_addr=7'd0; token_ep=2'd0; #1;
    check(!for_us, "and address 0 is not address 64");
    step;

    // 7. The highest address, exactly.
    dev_address = 7'd127;
    token_valid=1; token_addr=7'd127; token_ep=2'd0; #1;
    check(for_us, "address 127 matches itself");
    step;
    token_valid=1; token_addr=7'd126; token_ep=2'd0; #1;
    check(!for_us, "and does not match 126");
    step;
    token_valid=0;

    // ===== D. randomised =====
    hard_reset;
    for (i=0;i<40000;i=i+1) begin
      dev_address = 7'($urandom%128);
      // bias hard toward a matching address -- a random 7-bit pair matches
      // once in 128, and the interesting half of this block is downstream
      // of a match
      token_addr  = (($urandom%3)!=0) ? dev_address : 7'($urandom%128);
      token_ep    = EPW'($urandom%4);
      ep_enabled  = EP_N'($urandom%16);
      token_valid = ($urandom%8)!=0;
      step;
    end

    foreach (n_perep[i])
      check(n_perep[i] > 500, "every endpoint was selected many times");
    check(n_match   > 5000, "address matches happened often");
    check(n_nomatch > 5000, "address mismatches happened often");
    check(n_unk     > 2000, "unknown endpoints were addressed often");

    $display("");
    $display("  REACH: address-pairs=%0d ep-points=%0d | matched=%0d not-ours=%0d selected=%0d unknown-ep=%0d",
             n_addr_exh, n_ep_exh, n_match, n_nomatch, n_sel, n_unk);
    $display("  PER-ENDPOINT selects: ep0=%0d ep1=%0d ep2=%0d ep3=%0d",
             n_perep[0], n_perep[1], n_perep[2], n_perep[3]);
    $display("  COUNTERS: tokens=%0d for-us=%0d not-ours=%0d unknown-ep=%0d selected=%0d",
             n_tokens, n_for_us, n_not_ours, n_unknown_ep, n_selected);
    $display("  [SystemVerilog] usb_ep_decode: %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.usb_decode_pkg.all;

entity tb_dc_vhdl is
end entity;

architecture sim of tb_dc_vhdl is
  constant EP_N : natural := 4;
  constant EPW  : natural := 2;

  signal clk   : std_logic := '0';
  signal rst_n : std_logic := '0';
  signal token_valid : std_logic := '0';
  signal dev_address, token_addr : std_logic_vector(6 downto 0)
       := (others => '0');
  signal token_ep   : std_logic_vector(EPW-1 downto 0) := (others => '0');
  signal ep_enabled : std_logic_vector(EP_N-1 downto 0) := (others => '0');

  signal for_us, ep_exists, unknown_ep, silent : std_logic;
  signal ep_select : std_logic_vector(EP_N-1 downto 0);
  signal verdict   : std_logic_vector(1 downto 0);
  signal n_tokens, n_for_us, n_not_ours, n_unknown_ep, n_selected
       : 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.usb_ep_decode
    generic map (EP_N => EP_N, EPW => EPW)
    port map (clk => clk, rst_n => rst_n, dev_address => dev_address,
              token_valid => token_valid, token_addr => token_addr,
              token_ep => token_ep, ep_enabled => ep_enabled,
              for_us => for_us, ep_select => ep_select,
              ep_exists => ep_exists, unknown_ep => unknown_ep,
              silent => silent, verdict => verdict, n_tokens => n_tokens,
              n_for_us => n_for_us, n_not_ours => n_not_ours,
              n_unknown_ep => n_unknown_ep, n_selected => n_selected);

  stim : process
    variable seed1 : positive := 8419;
    variable seed2 : positive := 2617;
    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;
    variable n_addr_exh, n_ep_exh : integer := 0;
    variable n_match, n_nomatch, n_sel, n_unk : integer := 0;
    variable n_perep : cnt4_t := (others => 0);
    variable m_tok, m_us, m_no, m_unk, m_sel : integer := 0;

    -- count the set bits of a vector -- used for the one-hot property
    function popcount(v : std_logic_vector) return integer is
      variable c : integer := 0;
    begin
      for b in v'range loop
        if v(b) = '1' then c := c + 1; end if;
      end loop;
      return c;
    end function;

    procedure check(cond : boolean; msg : string) is
    begin
      if not cond then
        errors := errors + 1;
        if errors <= 25 then
          report "  FAIL: " & msg
               & " (vld=" & std_logic'image(token_valid)(2)
               & " dev=" & integer'image(to_integer(unsigned(dev_address)))
               & " tok=" & integer'image(to_integer(unsigned(token_addr)))
               & " ep=" & integer'image(to_integer(unsigned(token_ep)))
               & " | for_us=" & std_logic'image(for_us)(2)
               & " exists=" & std_logic'image(ep_exists)(2)
               & " unk=" & std_logic'image(unknown_ep)(2)
               & " verdict=" & integer'image(to_integer(unsigned(verdict)))
               & ")" 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;

    procedure check_comb is
      variable e_us, e_exists, e_unk : boolean;
      variable e_sel : std_logic_vector(EP_N-1 downto 0);
      variable e_verd : token_verdict_t;
      variable epi : integer;
    begin
      -- The model builds the one-hot select by a LOOP over the endpoints
      -- where the design indexes a variable -- a different route, and one
      -- that would happily produce two bits set if the rule allowed it.
      epi      := to_integer(unsigned(token_ep));
      e_us     := token_valid = '1' and token_addr = dev_address;
      e_exists := e_us and ep_enabled(epi) = '1';
      e_unk    := e_us and ep_enabled(epi) = '0';
      e_sel    := (others => '0');
      for b in 0 to EP_N-1 loop
        if e_exists and epi = b then e_sel(b) := '1'; end if;
      end loop;

      check((for_us = '1') = e_us,         "for_us matches the model");
      check((ep_exists = '1') = e_exists,  "ep_exists matches the model");
      check(ep_select = e_sel,             "ep_select matches the model");
      check((unknown_ep = '1') = e_unk,    "unknown_ep matches the model");
      check((silent = '1') = e_unk,        "silent matches unknown_ep");

      if token_valid = '0' then  e_verd := TOK_IDLE;
      elsif not e_us then        e_verd := TOK_NOT_OURS;
      elsif e_unk then           e_verd := TOK_NO_SUCH_EP;
      else                       e_verd := TOK_SELECTED;
      end if;
      check(verdict = verdict_code(e_verd), "verdict matches the model");
      -- The named verdict must agree with the signals it summarises.
      check((verdict = verdict_code(TOK_SELECTED))
            = (ep_select /= (ep_select'range => '0')),
            "verdict disagrees with ep_select");
      check((verdict = verdict_code(TOK_NO_SUCH_EP)) = (silent = '1'),
            "verdict disagrees with silent");

      -- ---- SAFETY PROPERTIES, independent of the model ----
      -- 1. THE property. The select is ONE-HOT or zero.
      check(popcount(ep_select) <= 1,
            "more than one endpoint was selected -- that is a bus contention inside the device");
      -- 2. A token for another device selects nothing and says nothing.
      if token_valid = '1' and token_addr /= dev_address then
        check(for_us = '0', "a token for another address was claimed");
        check(ep_select = (ep_select'range => '0'),
              "a token for another device selected an endpoint");
        check(silent = '0',
              "a token for another device produced a silence WE are responsible for");
      end if;
      -- 3. Address 0 is not a wildcard.
      if token_valid = '1' and to_integer(unsigned(token_addr)) = 0
         and to_integer(unsigned(dev_address)) /= 0 then
        check(for_us = '0',
              "an addressed device answered address 0 -- it will collide with the device being enumerated");
      end if;
      -- 4. The address is SEVEN bits. Address 64 must not alias to 0.
      if token_valid = '1' and to_integer(unsigned(token_addr)) = 64
         and to_integer(unsigned(dev_address)) = 0 then
        check(for_us = '0',
              "address 64 matched address 0 -- the compare is only six bits wide");
      end if;
      if token_valid = '1' and to_integer(unsigned(token_addr)) = 0
         and to_integer(unsigned(dev_address)) = 64 then
        check(for_us = '0', "address 0 matched address 64");
      end if;
      -- 5. A non-existent endpoint gets SILENCE, and selects nothing.
      if unknown_ep = '1' then
        check(ep_select = (ep_select'range => '0'),
              "a non-existent endpoint was selected");
        check(silent = '1',
              "a non-existent endpoint did not produce silence -- a STALL would claim it exists");
      end if;
      -- 6. Selecting and not-existing are mutually exclusive.
      check(not (ep_select /= (ep_select'range => '0') and unknown_ep = '1'),
            "an endpoint was both selected and unknown");
      -- 7. Nothing at all happens without a token.
      if token_valid = '0' then
        check(for_us = '0' and silent = '0'
              and ep_select = (ep_select'range => '0'),
              "the decode responded with no token present");
      end if;
      -- 8. A selected endpoint is the one the token named.
      if ep_select /= (ep_select'range => '0') then
        check(ep_select(epi) = '1',
              "the selected endpoint is not the one the token addressed");
      end if;

      if e_us then n_match := n_match + 1;
      elsif token_valid = '1' then n_nomatch := n_nomatch + 1; end if;
      if e_sel /= (e_sel'range => '0') then
        n_sel := n_sel + 1;
        n_perep(epi) := n_perep(epi) + 1;
      end if;
      if e_unk then n_unk := n_unk + 1; end if;
    end procedure;

    procedure step is
    begin
      wait for 1 ns;
      check_comb;
      if token_valid = '1' then
        m_tok := m_tok + 1;
        if for_us = '1' then m_us := m_us + 1; else m_no := m_no + 1; end if;
        if unknown_ep = '1' then m_unk := m_unk + 1; end if;
        if ep_select /= (ep_select'range => '0') then m_sel := m_sel + 1; end if;
      end if;
      wait until rising_edge(clk);
      wait for 1 ns;
      check(n_tokens = std_logic_vector(to_unsigned(m_tok, 32)),
            "n_tokens matches the model");
      check(n_for_us = std_logic_vector(to_unsigned(m_us, 32)),
            "n_for_us matches the model");
      check(n_not_ours = std_logic_vector(to_unsigned(m_no, 32)),
            "n_not_ours matches the model");
      check(n_unknown_ep = std_logic_vector(to_unsigned(m_unk, 32)),
            "n_unknown_ep matches the model");
      check(n_selected = std_logic_vector(to_unsigned(m_sel, 32)),
            "n_selected matches the model");
    end procedure;

    procedure hard_reset is
    begin
      rst_n <= '0'; token_valid <= '0';
      dev_address <= (others => '0'); token_addr <= (others => '0');
      token_ep <= (others => '0'); ep_enabled <= (others => '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;
      m_tok := 0; m_us := 0; m_no := 0; m_unk := 0; m_sel := 0;
    end procedure;

    variable iv, dva : integer;
  begin
    hard_reset;
    check(for_us = '0', "no token, no match");
    check(ep_select = (ep_select'range => '0'), "and nothing selected");

    -- ===== A. EXHAUSTIVE address-compare sweep =====
    -- Every one of the 128 device addresses against every one of the 128
    -- token addresses = 16384 points. That is the ENTIRE address domain,
    -- which is what it takes to prove the compare is seven bits wide and has
    -- no wildcard anywhere in it.
    token_valid <= '1'; ep_enabled <= (others => '1');
    token_ep <= (others => '0');
    for i in 0 to 127 loop
      for j in 0 to 127 loop
        dev_address <= std_logic_vector(to_unsigned(i, 7));
        token_addr  <= std_logic_vector(to_unsigned(j, 7));
        step;
        n_addr_exh := n_addr_exh + 1;
      end loop;
    end loop;
    token_valid <= '0';
    report "  exhaustive address-compare sweep: " & integer'image(n_addr_exh)
         & " of 16384 pairs verified" severity note;

    -- ===== B. EXHAUSTIVE endpoint-decode sweep =====
    -- Every endpoint-enable mask against every endpoint index, with the
    -- address matching and not matching, and with and without a token:
    --   16 masks x 4 endpoints x 2 (address match) x 2 (token_valid) = 256
    for m in 0 to 15 loop
      for k in 0 to 3 loop
        for i in 0 to 1 loop
          for v in 0 to 1 loop
            dev_address <= std_logic_vector(to_unsigned(37, 7));
            if i = 1 then
              token_addr <= std_logic_vector(to_unsigned(37, 7));
            else
              token_addr <= std_logic_vector(to_unsigned(38, 7));
            end if;
            token_ep   <= std_logic_vector(to_unsigned(k, EPW));
            ep_enabled <= std_logic_vector(to_unsigned(m, EP_N));
            if v = 1 then token_valid <= '1'; else token_valid <= '0'; end if;
            step;
            n_ep_exh := n_ep_exh + 1;
          end loop;
        end loop;
      end loop;
    end loop;
    token_valid <= '0';
    report "  exhaustive endpoint-decode sweep: " & integer'image(n_ep_exh)
         & " of 256 points verified" severity note;

    -- ===== C. directed: the cases that cause bus contention =====
    hard_reset;
    dev_address <= std_logic_vector(to_unsigned(9, 7));
    ep_enabled  <= "1011";

    -- 1. Our address, an endpoint we have.
    token_valid <= '1'; token_addr <= std_logic_vector(to_unsigned(9, 7));
    token_ep <= "01"; wait for 1 ns;
    check(for_us = '1', "a token for our address is ours");
    check(ep_exists = '1', "endpoint 1 exists");
    check(ep_select = "0010", "and exactly endpoint 1 is selected");
    step;

    -- 2. Our address, an endpoint we do NOT have.
    token_valid <= '1'; token_addr <= std_logic_vector(to_unsigned(9, 7));
    token_ep <= "10"; wait for 1 ns;
    check(for_us = '1', "still our address");
    check(ep_exists = '0', "but endpoint 2 does not exist on this device");
    check(ep_select = "0000", "so nothing is selected");
    check(silent = '1',
          "and the device says NOTHING -- a STALL would claim the endpoint exists");
    step;

    -- 3. Somebody else's address.
    token_valid <= '1'; token_addr <= std_logic_vector(to_unsigned(10, 7));
    token_ep <= "01"; wait for 1 ns;
    check(for_us = '0', "a token for address 10 is not ours");
    check(ep_select = "0000", "nothing selected");
    check(silent = '0', "and the silence is not ours to produce");
    step;

    -- 4. THE enumeration hazard.
    token_valid <= '1'; token_addr <= (others => '0'); token_ep <= "00";
    wait for 1 ns;
    check(for_us = '0',
          "an ADDRESSED device must not answer address 0 -- the device being enumerated is using it");
    check(ep_select = "0000", "so nothing is selected");
    step;

    -- 5. ...and while WE are unaddressed, address 0 IS ours.
    dev_address <= (others => '0');
    token_valid <= '1'; token_addr <= (others => '0'); token_ep <= "00";
    wait for 1 ns;
    check(for_us = '1',
          "a device in the DEFAULT state HAS address 0, so the plain equality covers it");
    check(ep_select = "0001", "and endpoint 0 is selected");
    step;

    -- 6. THE seven-bit hazard.
    dev_address <= (others => '0');
    token_valid <= '1'; token_addr <= std_logic_vector(to_unsigned(64, 7));
    token_ep <= "00"; wait for 1 ns;
    check(for_us = '0',
          "address 64 is NOT address 0 -- the compare is seven bits wide");
    step;
    dev_address <= std_logic_vector(to_unsigned(64, 7));
    token_valid <= '1'; token_addr <= (others => '0'); token_ep <= "00";
    wait for 1 ns;
    check(for_us = '0', "and address 0 is not address 64");
    step;

    -- 7. The highest address, exactly.
    dev_address <= std_logic_vector(to_unsigned(127, 7));
    token_valid <= '1'; token_addr <= std_logic_vector(to_unsigned(127, 7));
    token_ep <= "00"; wait for 1 ns;
    check(for_us = '1', "address 127 matches itself");
    step;
    token_valid <= '1'; token_addr <= std_logic_vector(to_unsigned(126, 7));
    wait for 1 ns;
    check(for_us = '0', "and does not match 126");
    step;
    token_valid <= '0';

    -- ===== 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
      -- The device address is kept in a VARIABLE as well as driven onto the
      -- signal, because the bias below needs the address chosen THIS
      -- iteration. Reading dev_address back would read the PREVIOUS value: a
      -- signal assignment does not take effect until the process waits, and
      -- the result is a run in which almost no address ever matches. This is
      -- the same trap as chapter 22.3 section 9, and it was made twice.
      rnd(dva, 128);
      dev_address <= std_logic_vector(to_unsigned(dva, 7));
      -- bias hard toward a matching address -- a random 7-bit pair matches
      -- once in 128, and the interesting half of this block is downstream of
      -- a match
      rnd(iv, 3);
      if iv /= 0 then
        token_addr <= std_logic_vector(to_unsigned(dva, 7));
      else
        rnd(iv, 128); token_addr <= std_logic_vector(to_unsigned(iv, 7));
      end if;
      rnd(iv, 4);  token_ep   <= std_logic_vector(to_unsigned(iv, EPW));
      rnd(iv, 16); ep_enabled <= std_logic_vector(to_unsigned(iv, EP_N));
      rnd(iv, 8);  if iv /= 0 then token_valid <= '1';
                   else token_valid <= '0'; end if;
      step;
    end loop;

    for i in 0 to 3 loop
      check(n_perep(i) > 500, "every endpoint was selected many times");
    end loop;
    check(n_match   > 5000, "address matches happened often");
    check(n_nomatch > 5000, "address mismatches happened often");
    check(n_unk     > 2000, "unknown endpoints were addressed often");

    report "  REACH: address-pairs=" & integer'image(n_addr_exh)
         & " ep-points=" & integer'image(n_ep_exh)
         & " | matched=" & integer'image(n_match)
         & " not-ours=" & integer'image(n_nomatch)
         & " selected=" & integer'image(n_sel)
         & " unknown-ep=" & integer'image(n_unk) severity note;
    report "  PER-ENDPOINT selects: ep0=" & integer'image(n_perep(0))
         & " ep1=" & integer'image(n_perep(1))
         & " ep2=" & integer'image(n_perep(2))
         & " ep3=" & integer'image(n_perep(3)) severity note;
    report "  COUNTERS: tokens="
         & integer'image(to_integer(unsigned(n_tokens)))
         & " for-us=" & integer'image(to_integer(unsigned(n_for_us)))
         & " not-ours=" & integer'image(to_integer(unsigned(n_not_ours)))
         & " unknown-ep=" & integer'image(to_integer(unsigned(n_unknown_ep)))
         & " selected=" & integer'image(to_integer(unsigned(n_selected)))
         severity note;
    report "  [VHDL] usb_ep_decode: " & 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
Address pairs16384 / 1638416384 / 1638416384 / 16384
Endpoint-decode points256 / 256256 / 256256 / 256
address matches235112352423617
address mismatches279602791427912
endpoints selected118191178911890
unknown endpoints addressed116921173511727
selects per endpoint (0/1/2/3)3065 / 2905 / 2892 / 29573020 / 2966 / 2844 / 29593044 / 2920 / 2955 / 2971
ResultPASSPASSPASS
#MutationVerilogSysVerVHDL
Q1address 0 is treated as a wildcard162215741560
Q2the select ignores whether the endpoint exists467694694146909
Q3the select ignores whether the token was ours664886589766149
Q4the select also sets bit 0 — two hot175091753917693
Q5for_us ignores token_valid173111719116841
Q6the address compare is six bits135413481306
Q7a valid endpoint also produces silence236382357823780
—unmutated baseline000

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

Q1 and Q6 are the two smallest and the two most dangerous. Q1 is wrong only when a token carries address 0 and the device has a different address — one address value out of 128. Q6 is wrong only for address pairs differing in bit 6 alone — 64 pairs out of 16 384. Both produce bus contention between two devices, which is the worst failure mode in this chapter, and both are near the bottom of the table.

Q3 is the largest at ~66 000, and it is the same bug as Q1 with the check removed entirely rather than widened. The ratio between them — 40× — is the ratio between "answers one extra address" and "answers all of them".

Q4 is the one-hot violation, at ~17 500. Note that it is caught by the model and by safety property 1, and it is worth having both: the model check would pass a design where the shift produced the wrong single bit, and the popcount check would pass a design that selected the wrong endpoint. Neither subsumes the other, and the design is wrong in different ways under each.

12. UVM, Assertions, and the Bus-Contention Property

12.1 The sequence that matters

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

  rand bit [6:0] dev_address;
  rand bit       token_valid;
  rand bit [6:0] token_addr;
  rand bit [1:0] token_ep;
  rand bit [3:0] ep_enabled;

  // A random 7-bit pair matches once in 128, and the whole interesting half
  // of this block is downstream of a match.
  constraint c_mostly_matching {
    token_addr dist { dev_address := 2, [0:127] := 1 };
  }

  // A real device has endpoint 0 and usually one or two more.
  constraint c_realistic_eps { ep_enabled[0] == 1; }

  constraint c_mostly_valid { token_valid dist {1 := 7, 0 := 1}; }

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

// THE sequence for this chapter. It reproduces the enumeration hazard: this
// device is ADDRESSED, and the host is talking to address 0 because another
// device has just been reset. Every one of these tokens must be ignored.
//
// Random stimulus reaches this by accident once in 128 tokens; the bug it
// finds causes two devices to drive the bus at once, so it is worth
// reaching on purpose.
class enumeration_hazard_seq extends uvm_sequence #(usb_token_item);
  `uvm_object_utils(enumeration_hazard_seq)
  function new(string name = "enumeration_hazard_seq"); super.new(name); endfunction

  task body();
    repeat (500) begin
      usb_token_item it = usb_token_item::type_id::create("it");
      start_item(it);
      it.c_mostly_matching.constraint_mode(0);
      if (!it.randomize() with { token_valid == 1;
                                 dev_address != 7'd0;   // we ARE addressed
                                 token_addr  == 7'd0; })// ...and this is not ours
        `uvm_error("RAND", "enumeration-hazard randomize failed")
      finish_item(it);
    end
  endtask
endclass

// The seven-bit hazard: address pairs that differ in bit 6 ALONE. A six-bit
// compare makes every one of these a match, and there are only 64 of them in
// the whole 16384-pair space.
class bit6_alias_seq extends uvm_sequence #(usb_token_item);
  `uvm_object_utils(bit6_alias_seq)
  function new(string name = "bit6_alias_seq"); super.new(name); endfunction

  task body();
    for (int a = 0; a < 64; a++) begin
      usb_token_item it = usb_token_item::type_id::create("it");
      start_item(it);
      it.c_mostly_matching.constraint_mode(0);
      if (!it.randomize() with { token_valid == 1;
                                 dev_address == a;
                                 token_addr  == (a + 64); })
        `uvm_error("RAND", "bit6 randomize failed")
      finish_item(it);
    end
  endtask
endclass

// Tokens for endpoints this device does not implement. Each must produce
// SILENCE -- a STALL would tell the host the endpoint exists.
class phantom_endpoint_seq extends uvm_sequence #(usb_token_item);
  `uvm_object_utils(phantom_endpoint_seq)
  function new(string name = "phantom_endpoint_seq"); super.new(name); endfunction

  task body();
    repeat (400) begin
      usb_token_item it = usb_token_item::type_id::create("it");
      start_item(it);
      it.c_mostly_matching.constraint_mode(0);
      if (!it.randomize() with { token_valid == 1;
                                 token_addr == dev_address;   // ours
                                 ep_enabled[token_ep] == 0; })// but absent
        `uvm_error("RAND", "phantom randomize failed")
      finish_item(it);
    end
  endtask
endclass

12.2 The scoreboard

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

  uvm_analysis_imp #(usb_decode_mon_item, usb_decode_scoreboard) ap;

  int unsigned n_ours, n_theirs, n_phantom, n_selected;
  int unsigned n_per_ep[4];

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

  function automatic int popcount(bit [3:0] v);
    int c = 0;
    foreach (v[i]) if (v[i]) c++;
    return c;
  endfunction

  function void write(usb_decode_mon_item t);
    bit ours    = t.token_valid && (t.token_addr == t.dev_address);
    bit exists  = ours && t.ep_enabled[t.token_ep];
    bit phantom = ours && !t.ep_enabled[t.token_ep];

    // ---- THE property. The select is ONE-HOT or zero, always. Two
    // ---- endpoint blocks driving the shared datapath is a contention
    // ---- INSIDE the device, and nothing on the bus will reveal it.
    if (popcount(t.ep_select) > 1)
      `uvm_error("CONTENTION",
        $sformatf("ep_select=%b has %0d bits set -- two endpoint blocks are driving the shared datapath",
                  t.ep_select, popcount(t.ep_select)))

    // ---- A token for another device is answered by NOTHING ----
    if (t.token_valid && !ours) begin
      if (t.for_us)
        `uvm_error("ADDRESS",
          $sformatf("claimed a token for address %0d while at address %0d -- two devices will drive the bus together",
                    t.token_addr, t.dev_address))
      if (t.ep_select != '0)
        `uvm_error("ADDRESS", "selected an endpoint for another device's token")
      if (t.silent)
        `uvm_error("ADDRESS",
          "produced a silence we are not responsible for -- another device is answering this")
      n_theirs++;
    end

    // ---- Address 0 is not a wildcard ----
    if (t.token_valid && (t.token_addr == 7'd0) && (t.dev_address != 7'd0)
        && t.for_us)
      `uvm_error("ENUMERATION",
        "an addressed device answered address 0 -- it is colliding with the device being enumerated")

    // ---- The compare is SEVEN bits ----
    if (t.token_valid && t.for_us && (t.token_addr[6] != t.dev_address[6]))
      `uvm_error("WIDTH",
        "addresses differing in bit 6 matched -- the compare is only six bits wide")

    // ---- A phantom endpoint gets SILENCE, never a select ----
    if (phantom) begin
      if (t.ep_select != '0)
        `uvm_error("PHANTOM", "selected an endpoint this device does not have")
      if (!t.silent)
        `uvm_error("PHANTOM",
          "a non-existent endpoint did not produce silence -- a STALL would tell the host it exists and the host will clear the halt for ever")
      n_phantom++;
    end

    if (exists) begin
      if (!t.ep_select[t.token_ep])
        `uvm_error("SELECT", "the wrong endpoint was selected")
      n_selected++;
      n_per_ep[t.token_ep]++;
    end
    if (ours) n_ours++;
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("SB", $sformatf("ours=%0d theirs=%0d phantom=%0d selected=%0d",
                              n_ours, n_theirs, n_phantom, n_selected), UVM_LOW)
    if (n_theirs  == 0) `uvm_error("COVERAGE",
      "no token for another device was ever presented -- the address compare is untested")
    if (n_phantom == 0) `uvm_error("COVERAGE",
      "no token for a non-existent endpoint was ever presented")
    foreach (n_per_ep[i])
      if (n_per_ep[i] == 0)
        `uvm_error("COVERAGE",
          $sformatf("endpoint %0d was never selected", i))
  endfunction
endclass

12.3 Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module usb_ep_decode_sva
  import usb_decode_pkg::*;
#(
  parameter int EP_N = 4,
  parameter int EPW  = 2
) (
  input logic            clk,
  input logic            rst_n,
  input logic [6:0]      dev_address,
  input logic            token_valid,
  input logic [6:0]      token_addr,
  input logic [EPW-1:0]  token_ep,
  input logic [EP_N-1:0] ep_enabled,
  input logic            for_us,
  input logic [EP_N-1:0] ep_select,
  input logic            ep_exists,
  input logic            unknown_ep,
  input logic            silent,
  input token_verdict_e  verdict
);
  default clocking cb @(posedge clk); endclocking
  default disable iff (!rst_n);

  // ---- 1. THE property. The select is ONE-HOT or zero. ----
  property p_select_is_onehot0;
    $onehot0(ep_select);
  endproperty
  a_select_is_onehot0 : assert property (p_select_is_onehot0)
    else $error("ep_select=%b -- two endpoint blocks are driving the shared datapath",
                ep_select);

  // ---- 2. The address compare is an EQUALITY, in seven bits. ----
  property p_for_us_iff_exact_match;
    for_us == (token_valid && (token_addr == dev_address));
  endproperty
  a_for_us_iff_exact_match : assert property (p_for_us_iff_exact_match)
    else $error("the address compare is not a plain 7-bit equality");

  // ---- 3. An addressed device never answers address 0. ----
  property p_no_address_zero_wildcard;
    (token_valid && (token_addr == 7'd0) && (dev_address != 7'd0))
      |-> !for_us;
  endproperty
  a_no_address_zero_wildcard :
    assert property (p_no_address_zero_wildcard)
    else $error("an addressed device answered address 0 -- bus contention with the device being enumerated");

  // ---- 4. Addresses differing in bit 6 alone never match. ----
  property p_seven_bit_compare;
    (token_valid && (token_addr[6] != dev_address[6])) |-> !for_us;
  endproperty
  a_seven_bit_compare : assert property (p_seven_bit_compare)
    else $error("address bit 6 was ignored -- address 64 aliases to address 0");

  // ---- 5. A token for another device produces NOTHING. ----
  property p_others_token_is_ignored;
    (token_valid && !for_us) |-> ((ep_select == '0) && !silent);
  endproperty
  a_others_token_is_ignored :
    assert property (p_others_token_is_ignored);

  // ---- 6. A phantom endpoint gets silence and no select. ----
  property p_phantom_is_silent;
    unknown_ep |-> ((ep_select == '0) && silent);
  endproperty
  a_phantom_is_silent : assert property (p_phantom_is_silent)
    else $error("a non-existent endpoint was answered -- a STALL claims it exists");

  // ---- 7. A selected endpoint is the one the token named, and it is
  // ----    one this device implements.
  property p_select_is_the_named_endpoint;
    (ep_select != '0) |-> (ep_select[token_ep] && ep_enabled[token_ep]);
  endproperty
  a_select_is_the_named_endpoint :
    assert property (p_select_is_the_named_endpoint);

  // ---- 8. Nothing happens with no token. ----
  property p_idle_is_quiet;
    !token_valid |-> (!for_us && !silent && (ep_select == '0));
  endproperty
  a_idle_is_quiet : assert property (p_idle_is_quiet);

  // ---- 9. The named verdict agrees with the signals it summarises. ----
  property p_verdict_agrees;
    ((verdict == TOK_SELECTED)   == (ep_select != '0))
    && ((verdict == TOK_NO_SUCH_EP) == silent)
    && ((verdict == TOK_IDLE)    == !token_valid);
  endproperty
  a_verdict_agrees : assert property (p_verdict_agrees);

  // ---- Cover: the narrow, dangerous populations were reached. ----
  c_addr_zero_not_ours : cover property ((token_valid
                            && (token_addr == 7'd0) && (dev_address != 7'd0)));
  c_bit6_pair          : cover property ((token_valid
                            && (token_addr[5:0] == dev_address[5:0])
                            && (token_addr[6] != dev_address[6])));
  c_phantom            : cover property ((unknown_ep));
  c_each_ep            : cover property ((ep_select != '0));
endmodule

bind usb_ep_decode usb_ep_decode_sva #(.EP_N(EP_N), .EPW(EPW)) u_sva (.*);

13. Common Misconceptions

"The hub routes tokens to the right device." A USB 2.0 hub in high-speed mode repeats downstream traffic to every enabled port. The device does the filtering.

"Address 0 is a broadcast address." It is the address of a device in the DEFAULT state. Exactly one device may hold it at a time, and an addressed device must not answer it.

"Responding to an unknown endpoint with a STALL is the polite answer." It tells the host the endpoint exists and is halted, so the host clears the halt and retries for ever. Silence is the honest answer.

"A wrong endpoint select just sends data to the wrong place." In a comparison-built decoder it selects two endpoints, and what reaches the shared datapath depends on the technology rather than on the logic.

"Seven bits versus six only matters above 64 devices." It matters above 64 addresses in use, and a hub tree hands out addresses without reusing them within a session.

"token_valid is redundant — the address will not match when idle." It will match whenever the stale address bus happens to hold our address, which is most of the time on a bus that just talked to us. Mutation Q5, ~17 300 failures.

14. Exercises

1. Widen the address compare to (token_addr == dev_address) || (token_addr == 0) and predict which of the eight safety properties fires first. Then explain why the count (~1600) is so much smaller than Q3's (~66 000) despite both being address bugs.

2. Q6 is wrong on 64 of 16 384 address pairs. Compute the probability that a 1000-token random regression contains at least one of them, and use the answer to argue for the exhaustive sweep.

3. Build the endpoint select from four comparisons instead of a shift, introduce a typo in one of them, and confirm that safety property 1 catches it while the model check does not. Then find the mutation the model check catches and property 1 does not.

4. Add a second device instance on the same inputs, at a different address, and write the assertion that at most one of them asserts for_us. Which of this chapter's mutations does that assertion catch that the single-instance properties do not?

5. silent and unknown_ep are the same signal. Is one of them dead? Apply the dead-versus-unreachable test from this curriculum's mutation discipline and justify keeping or removing it.

6. The VHDL testbench read a signal back in the iteration that drove it, producing 454 matches instead of 23 500. Write the reach assertion that catches it in one line, and say why "the model checks all passed" was not enough.

15. Summary

IdeaWhy it matters
Every device sees every tokenfive devices staying silent is the only thing preventing contention
The address compare is a plain equalityno wildcard, no broadcast, no "just in case"
An addressed device never answers address 0enumeration needs exactly one device holding it
The address is seven bitssix bits makes address 64 alias to 0
A non-existent endpoint gets silencea STALL claims it exists and is merely halted
The select is one-hot by constructionbuilt by a shift, not by comparisons
Three outcomes all mean "drive nothing"and they are three different facts
16384 address pairs, exhaustivelythe dangerous bugs are specific pairs
7 mutations, all killed in 3 languagesthe two most dangerous are the two smallest counts

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o dc_v.out dc_v.v dc_v_tb.v && ./dc_v.out
SystemVerilogiverilog -g2012 -o dc_sv.out dc_sv.sv dc_sv_tb.sv && ./dc_sv.out
VHDL-2008 analysenvc --std=2008 -a dc_vhdl.vhd dc_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_dc_vhdl
VHDL-2008 runnvc --std=2008 -r tb_dc_vhdl
One mutationiverilog -g2005 -DMUT_Q1 -o mm dc_v_mut.v dc_v_tb.v && ./mm

All three implementations pass with 0 errors: 16 384 of 16 384 address pairs, 256 of 256 endpoint-decode points, 40 000 randomised tokens, every endpoint selected and asserted selected.


Chapter 23.2 — Endpoint RTL takes the one-hot select this block produces and asks what happens when several endpoints want the shared FIFO port at the same time. The answer is a round-robin arbiter — and its defining property is not fairness in the vague sense but bounded waiting: no requester ever waits more than N−1 grants, which is a property you can enumerate rather than argue about.

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.