Skip to content
VLSI Mentor

USB · Module 30

Integration Review Checklist

A correct, conformant USB controller still has to live inside somebody else’s chip. Clocks it does not own, four kinds of reset, information crossing domains, and firmware written against a datasheet — reviewed through an interrupt register whose set-and-clear ordering is the whole problem.

The controller is right and it conforms. Now it is one block among thirty on a die, and the failures move: they stop being about USB and start being about everything the controller assumed and nobody wrote down.

1. The Question

What does this block assume about the chip around it, and who is responsible for making each assumption true?

Almost every integration bug is an assumption held by one team and not by the other. The controller assumes its clock is running; the power team gates it. The controller assumes firmware clears the interrupt; firmware assumes reading it is enough. Neither side is wrong about its own block.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE SIX SEAMS, and every one of them has two owners

    clocks        who sources each one, can it stop, can it change
    resets        four different events, four different scopes
    domains       what crosses, and as what kind of information
    the register  what firmware sees, when, and with what side effects
      interface
    memory        who owns a buffer at each instant
    power         what survives a state change and what does not

2. Clocks — Who Owns Them, And Can They Stop?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every clock in the block has a named source, a
                       stated frequency range, and a documented answer to
                       "can it stop, and if so when"
    EVIDENCE           the clock list, with a source and an owner per entry
    FAILURE SIGNATURE  the block works until somebody enables clock gating
                       in the power management unit, months later
    FALSE CONFIDENCE   "there is one clock" -- a USB controller never has
                       one clock
    NEXT               for each clock that can stop, ask what state is lost
                       and what is merely paused

A full-speed device controller typically has at least three, and they belong to three different people:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    CLOCK           SOURCED BY        CAN IT STOP?
    --------------  ----------------  -----------------------------------
    the USB clock   a PLL locked to   yes, during suspend -- which is the
    (48 MHz)        a crystal          whole point of suspend
    the bus clock   the SoC fabric    yes, whenever the fabric is gated
    the PHY clock   the PHY           often continuous, sometimes not

29.6 added a fourth kind: a clock sourced off-chip entirely, by a bridge chip, where the FPGA is the slave of somebody else's oscillator and cannot influence it at all.

3. Resets — Four Events, Four Scopes

29.5 established the distinction and 30.1 reviewed it inside one block. At integration the question changes again: which reset domain owns each piece of state, and does every writer of that state know when it is cleared?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    RESET            SCOPE                          WHO MUST KNOW
    ---------------  -----------------------------  -------------------
    power-on / SoC   everything, config included    nobody -- nothing
                                                    exists yet
    USB bus reset    the endpoint DATA state and    FIRMWARE, which holds
                     every data toggle; NOT the     a mental model of the
                     endpoint configuration         device state
    endpoint halt    one endpoint's toggle          firmware, and the host
      cleared
    software reset   whatever the datasheet says    everybody, and the
                                                    datasheet is often
                                                    the only record
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every register belongs to exactly one reset domain
                       for each reset TYPE, with a reason
    EVIDENCE           a table: register x reset type, with cleared /
                       preserved / undefined in each cell
    FAILURE SIGNATURE  firmware and hardware disagree about the device
                       state after a reset the host caused and firmware
                       did not observe
    FALSE CONFIDENCE   "reset is connected"
    NEXT               for every PRESERVED cell, ask whether the writer of
                       that register knows the reset happened

The last item is the integration-specific one, and it is where 30.1's F1 becomes a system problem rather than a block problem. A bus reset that preserves the endpoint configuration is correct — and it means firmware's picture of the device state is now stale in a way firmware has no event for, unless the controller raises one. Which is what the interrupt in the next section is for.

4. Domains — What Crosses, And As What Kind Of Information?

The single most damaging habit in integration review is treating "crossing a clock domain" as one problem with one solution. It is six problems, and the synchroniser that is right for one is wrong for the others.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    WHAT IS CROSSING          CORRECT MECHANISM           WRONG ANSWER
    ------------------------  --------------------------  ---------------
    a LEVEL that is stable    two flip-flops              none -- this is
    for many cycles                                       the only case
                                                          two flops fit

    a PULSE, one cycle wide   toggle synchroniser, or a   two flops: the
                              handshake                   pulse can be
                                                          missed entirely

    an EVENT that must not    handshake with an           two flops, which
    be lost                   acknowledgement             loses it under
                                                          the wrong phase

    a MULTI-BIT value that    Gray code, or a stable      two flops PER
    changes incrementally     bus plus a handshake        BIT, which can
                                                          sample a value
                                                          that never existed

    a MULTI-BIT value that    a stable bus plus a         two flops per bit
    changes arbitrarily       handshake, or an async      -- same failure,
                              FIFO                        more likely

    a STREAM                  an asynchronous FIFO with   anything simpler
                              Gray-coded pointers

5. The Register Interface — The Specimen

The seam where the controller meets firmware is a handful of registers, and the review question there is the one from 30.1's collision item, at a different level: two independent agents write to the same bits, and one of them is software.

The specimen is an interrupt status register. Six sticky event bits, a mask, and a level to the CPU.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    A NOTE ON THE WORD, because it causes real confusion in review
    meetings: the `irq` output here is an SoC INTERRUPT REQUEST to a CPU.
    It has nothing whatever to do with a USB INTERRUPT TRANSFER, which is
    a host-scheduled polling mechanism on the wire. They share a word and
    share nothing else.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_irq_status -- the interrupt status register at the seam between
//  a USB controller and the CPU it interrupts.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL. Six events,
//  one sticky bit each, a mask, and a level to the interrupt
//  controller. That is all it is, and it is where an integration review
//  spends more time than its size suggests -- because two independent
//  agents write to the same six bits and one of them is software.
//
//  A NOTE ON THE WORD, because it causes real confusion: the `irq`
//  output here is an SoC INTERRUPT REQUEST to the CPU. It has nothing
//  to do with a USB INTERRUPT TRANSFER, which is a host-scheduled
//  polling mechanism on the wire. The two share a word and share
//  nothing else. See 30.4 section 6.
//
//  THE THREE DECISIONS
//    1. A hardware SET beats a firmware CLEAR of the same bit in the
//       same cycle. Firmware's write reflects what it read a few cycles
//       ago; the event is newer than the decision to clear it.
//    2. Masking HIDES an event, it does not discard one. A masked event
//       still sets its status bit, and unmasking reveals it.
//    3. A sticky bit cannot count. Two events before firmware services
//       the first are one bit -- so the MULTIPLICITY is genuinely lost,
//       and the only honest response is to make the loss visible.
// =====================================================================
module usb_irq_status #(
  parameter integer NEV = 6
) (
  input  wire            clk,
  input  wire            rst_n,

  // One-cycle pulses from the controller. Bit assignments:
  //   0 setup received   1 OUT transfer done   2 IN transfer done
  //   3 USB bus reset    4 suspend             5 error
  input  wire [NEV-1:0]  ev,

  // ---- the firmware bus ----
  input  wire            fw_we,
  input  wire [1:0]      fw_addr,
  input  wire [15:0]     fw_wdata,
  output wire [15:0]     fw_rdata,

  output wire            irq,
  output wire [15:0]     n_lost
);

  localparam [1:0] A_STATUS = 2'd0,   // read sticky; write 1 to clear
                   A_MASK   = 2'd1,   // read/write
                   A_PEND   = 2'd2,   // read-only: status & mask
                   A_LOST   = 2'd3;   // read-only

  reg [NEV-1:0] sts;
  reg [NEV-1:0] msk;
  reg [15:0]    c_lost;

  // Firmware's write-one-to-clear, decoded once.
  wire [NEV-1:0] fw_clr =
      (fw_we && fw_addr == A_STATUS) ? fw_wdata[NEV-1:0] : {NEV{1'b0}};

  // [3] An event arriving for a bit that is ALREADY set. The bit cannot
  // represent two, so the second one's existence is recorded here
  // instead. A design that stays silent about this is not wrong, but
  // firmware can then never tell "one packet" from "three".
  wire [NEV-1:0] coincide = ev & sts & ~fw_clr;

  integer i;
  reg [15:0] lost_add;
  always @(*) begin
    lost_add = 16'd0;
    for (i = 0; i < NEV; i = i + 1)
      if (coincide[i]) lost_add = lost_add + 16'd1;
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      sts    <= {NEV{1'b0}};
      msk    <= {NEV{1'b0}};
      c_lost <= 16'd0;
    end else begin
      // [1] ONE expression, and the ORDER inside it is the decision.
      // The clear is applied to the OLD value and the event is set
      // afterwards, so a set and a clear of the same bit in the same
      // cycle leave the bit SET. Reverse the two -- (sts | ev) & ~clr --
      // and the clear wins instead: the event that arrived during
      // firmware's read-modify-write is gone, and nothing records that
      // it ever existed. That form reads just as natural and is the
      // defect this module exists to demonstrate.
      sts <= (sts & ~fw_clr) | ev;

      // [2] A write to MASK changes the mask and NOTHING ELSE. Clearing
      // status here is a real and common bug: it makes masking destroy
      // events rather than hide them, so a driver that masks during a
      // critical section loses every event that occurred inside it.
      if (fw_we && fw_addr == A_MASK) msk <= fw_wdata[NEV-1:0];

      c_lost <= c_lost + lost_add;
    end
  end

  // [4] The interrupt is a LEVEL derived from the two registers, with
  // no state of its own. A registered irq is one cycle late, which is
  // harmless here, but a registered irq that is SET and CLEARED by
  // different conditions is a third copy of the same fact.
  assign irq = |(sts & msk);

  assign fw_rdata =
      (fw_addr == A_STATUS) ? {{(16-NEV){1'b0}}, sts}         :
      (fw_addr == A_MASK)   ? {{(16-NEV){1'b0}}, msk}         :
      (fw_addr == A_PEND)   ? {{(16-NEV){1'b0}}, (sts & msk)} :
                              c_lost;

  assign n_lost = c_lost;

endmodule

6. The Three Decisions, And The One That Was Wrong

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  A hardware SET beats a firmware CLEAR of the same bit in the same
       cycle. Firmware's write reflects what it read several cycles ago;
       the event is newer than the decision to clear it.

    2  Masking HIDES an event, it does not DISCARD one. A masked event
       still sets its status bit, and unmasking reveals it.

    3  A sticky bit cannot count. Two events before firmware services the
       first are one bit -- so the multiplicity is genuinely lost, and the
       only honest response is to make the loss visible.

Decision 1 is one expression, and the order inside it is the entire decision:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      sts <= (sts & ~fw_clr) | ev;

The clear applies to the old value; the event is set afterwards. Reverse the two operations and the clear wins instead:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      sts <= (sts | ev) & ~fw_clr;      // WRONG, and it reads identically

An event arriving in the same cycle as firmware's clear

A waveform showing a hardware event arriving in the same cycle as a firmware write-one-to-clear of the same status bit. The bit is set by an event at cycle one. Firmware writes to clear it at cycle three, and a second event for the same bit occurs on that same cycle. In the wrong ordering the status bit goes to zero and the interrupt drops, losing the second event. In the correct ordering the status bit remains set, the interrupt remains asserted, and firmware is interrupted again to service the second event.first eventfirst eventcollisioncollisionafterafterfirst event sets the bitfirst event sets the bitclear and a second event collideclear and a second eventcollidewrong ordering: the event is gonewrong ordering: the eventis goneclkev[2]fw clear[2]sts[2] (wrong)irq (wrong)sts[2] (right)irq (right)t0t1t2t3t4t5t6t7
Firmware read the status at cycle 1, decided to clear bit 2, and its write lands at cycle 3 — by which time a second event has occurred. With the clear applied last the bit goes to zero and the event is gone with no record. With the clear applied to the old value and the event set afterwards, the bit stays set, the interrupt stays asserted, and firmware is called again. Neither version produces a wrong value anywhere; one of them simply loses an event.

7. Interrupts — The Review Items

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          an event that occurred is either serviced or
                       recorded as lost; it is never silently discarded
    EVIDENCE           the set/clear ordering, the mask semantics, and a
                       loss counter
    FAILURE SIGNATURE  a device that "occasionally stops responding" under
                       load, recovers on the next transfer, and cannot be
                       reproduced with a debugger attached -- because the
                       debugger changes firmware's timing
    FALSE CONFIDENCE   "we tested the interrupt" -- almost certainly with
                       the two agents on different cycles
    NEXT               ask what firmware does between reading and writing,
                       and how long it takes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    ITEM                                      WHY IT MATTERS
    ----------------------------------------  --------------------------
    set beats clear, per bit, in ONE           the event is newer than
    expression                                 the decision to clear it
    masking does not clear                     a driver that masks during
                                               a critical section must
                                               not lose what happened
    a mask write has no other side effect      two registers, two writes
    the interrupt is DERIVED from status and   a third copy of the same
    mask, with no state of its own             fact can disagree
    a loss counter exists                      a sticky bit cannot count,
                                               and pretending otherwise
                                               is the silent failure
    every bit is independent                   a shared clear or a
                                               mis-indexed mask passes a
                                               one-bit test

The last one is why the specimen's sweep runs over all six bits rather than one. "The bits are independent" is a claim about the design, and a per-bit sweep is what turns it into a measurement.

8. Buffer Ownership And DMA

29.5 built the ownership mechanism; at integration the question is who else can reach the buffer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          at every instant, exactly one agent may write each
                       buffer, and the transfer of that right is a single
                       observable event
    EVIDENCE           name the agents -- controller, DMA engine, CPU,
                       cache -- and the event that transfers ownership
                       between each pair
    FAILURE SIGNATURE  data that is correct when read by a debugger and
                       wrong when read by the application, or vice versa
    FALSE CONFIDENCE   "the ownership bit handles it" -- it handles the
                       controller and firmware; it says nothing about a
                       cache
    NEXT               ask what happens to ownership on error, on
                       cancellation, and on reset
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    QUESTION                        WHY IT IS ASKED SEPARATELY
    ------------------------------  ----------------------------------
    who owns it now?                the base case, usually answered
    what transfers ownership?       must be ONE event, not a sequence
    can both sides access it?       the failure that corrupts
    can NEITHER side own it?        the failure that hangs -- rarer and
                                    much harder to find
    what happens on reset?          a bus reset mid-transfer leaves a
                                    descriptor owned by hardware that
                                    will never complete it
    what happens on error?          same question, different trigger
    what happens on cancellation?   firmware aborting a transfer must
                                    reclaim ownership, and the
                                    controller must agree
    is the memory cacheable?        if so, ownership transfer needs a
                                    cache maintenance operation, and it
                                    is firmware's job, and it is the
                                    commonest DMA bug in any protocol

The fourth is the one reviews skip. A buffer owned by nobody is not a corruption — it is a hang, and it presents as "the device stopped" with every register looking reasonable.

9. Power States

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    LAYER                 HAS ITS OWN STATE MACHINE, AND THEY ARE NOT THE
                          SAME MACHINE
    --------------------  ------------------------------------------------
    USB protocol state    Default / Address / Configured / Suspended
    controller state      whatever the datasheet defines
    PHY state             powered, suspended, disconnected
    SoC power domain      on, retained, off
    firmware policy       when the driver decides to allow any of it
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every layer's transitions are legal from the state
                       every other layer is in
    EVIDENCE           the cross product, or an argument for why it does
                       not need enumerating
    FAILURE SIGNATURE  a device that suspends correctly and resumes into a
                       state nothing expected
    FALSE CONFIDENCE   "suspend works"
    NEXT               ask which layer notices first when the host
                       resumes, and whether it can

The specific USB trap: the device must respond to resume signalling while its main clock is stopped. Whatever detects resume cannot be clocked by the clock that suspend stops — which is a clocking requirement generated by a power requirement generated by a protocol requirement, three layers away from where it is implemented.

10. SystemVerilog And VHDL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_irq_status_sv -- the same register, in SystemVerilog. Identical
//  contract: same ports, same ordering decision, same reset, same
//  latency.
//
//  The one thing the type system adds here is the $countones on the
//  coincidence vector, which replaces a hand-written loop. That is a
//  smaller win than it looks and it is worth saying so: the loop was
//  never the risk. The risk is the ORDER of the set and the clear, and
//  no type system in any language will tell you which way round it
//  should be. See 30.4 section 5.
//
//  ------------------------------------------------------------------
//  The interrupt status register at the seam between
//  a USB controller and the CPU it interrupts.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL. Six events,
//  one sticky bit each, a mask, and a level to the interrupt
//  controller. That is all it is, and it is where an integration review
//  spends more time than its size suggests -- because two independent
//  agents write to the same six bits and one of them is software.
//
//  A NOTE ON THE WORD, because it causes real confusion: the `irq`
//  output here is an SoC INTERRUPT REQUEST to the CPU. It has nothing
//  to do with a USB INTERRUPT TRANSFER, which is a host-scheduled
//  polling mechanism on the wire. The two share a word and share
//  nothing else. See 30.4 section 6.
//
//  THE THREE DECISIONS
//    1. A hardware SET beats a firmware CLEAR of the same bit in the
//       same cycle. Firmware's write reflects what it read a few cycles
//       ago; the event is newer than the decision to clear it.
//    2. Masking HIDES an event, it does not discard one. A masked event
//       still sets its status bit, and unmasking reveals it.
//    3. A sticky bit cannot count. Two events before firmware services
//       the first are one bit -- so the MULTIPLICITY is genuinely lost,
//       and the only honest response is to make the loss visible.
// =====================================================================
module usb_irq_status_sv #(
  parameter int NEV = 6
) (
  input  logic            clk,
  input  logic            rst_n,

  // One-cycle pulses from the controller. Bit assignments:
  //   0 setup received   1 OUT transfer done   2 IN transfer done
  //   3 USB bus reset    4 suspend             5 error
  input  logic [NEV-1:0]  ev,

  // ---- the firmware bus ----
  input  logic            fw_we,
  input  logic [1:0]      fw_addr,
  input  logic [15:0]     fw_wdata,
  output logic [15:0]     fw_rdata,

  output logic            irq,
  output logic [15:0]     n_lost
);

  localparam [1:0] A_STATUS = 2'd0,   // read sticky; write 1 to clear
                   A_MASK   = 2'd1,   // read/write
                   A_PEND   = 2'd2,   // read-only: status & mask
                   A_LOST   = 2'd3;   // read-only

  logic [NEV-1:0] sts;
  logic [NEV-1:0] msk;
  logic [15:0]    c_lost;

  // Firmware's write-one-to-clear, decoded once.
  logic [NEV-1:0] fw_clr;
  assign fw_clr =
      (fw_we && fw_addr == A_STATUS) ? fw_wdata[NEV-1:0] : {NEV{1'b0}};

  // [3] An event arriving for a bit that is ALREADY set. The bit cannot
  // represent two, so the second one's existence is recorded here
  // instead. A design that stays silent about this is not wrong, but
  // firmware can then never tell "one packet" from "three".
  logic [NEV-1:0] coincide;
  assign coincide = ev & sts & ~fw_clr;

  logic [15:0] lost_add;
  assign lost_add = 16'($countones(coincide));

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      sts    <= {NEV{1'b0}};
      msk    <= {NEV{1'b0}};
      c_lost <= 16'd0;
    end else begin
      // [1] ONE expression, and the ORDER inside it is the decision.
      // The clear is applied to the OLD value and the event is set
      // afterwards, so a set and a clear of the same bit in the same
      // cycle leave the bit SET. Reverse the two -- (sts | ev) & ~clr --
      // and the clear wins instead: the event that arrived during
      // firmware's read-modify-write is gone, and nothing records that
      // it ever existed. That form reads just as natural and is the
      // defect this module exists to demonstrate.
      sts <= (sts & ~fw_clr) | ev;

      // [2] A write to MASK changes the mask and NOTHING ELSE. Clearing
      // status here is a real and common bug: it makes masking destroy
      // events rather than hide them, so a driver that masks during a
      // critical section loses every event that occurred inside it.
      if (fw_we && fw_addr == A_MASK) msk <= fw_wdata[NEV-1:0];

      c_lost <= c_lost + lost_add;
    end
  end

  // [4] The interrupt is a LEVEL derived from the two registers, with
  // no state of its own. A registered irq is one cycle late, which is
  // harmless here, but a registered irq that is SET and CLEARED by
  // different conditions is a third copy of the same fact.
  assign irq = |(sts & msk);

  always_comb begin
    unique case (fw_addr)
      A_STATUS: fw_rdata = {{(16-NEV){1'b0}}, sts};
      A_MASK:   fw_rdata = {{(16-NEV){1'b0}}, msk};
      A_PEND:   fw_rdata = {{(16-NEV){1'b0}}, (sts & msk)};
      A_LOST:   fw_rdata = c_lost;
    endcase
  end

  assign n_lost = c_lost;


`ifdef SVA_ON
  // Five properties, each one a review question from 30.4.
  // Icarus rejects SVA; under Icarus each is enforced by the named
  // procedural check in the testbench.

  // THE ORDERING DECISION, stated as a property. This is the one that
  // fails on the natural-looking wrong expression.
  property p_set_beats_clear;
    @(posedge clk) disable iff (!rst_n)
      |(ev & fw_clr) |=> ((sts & $past(ev & fw_clr)) == $past(ev & fw_clr));
  endproperty
  a_set_beats_clear: assert property (p_set_beats_clear);

  // MASKING HIDES. A write to the mask changes the mask and nothing else.
  property p_mask_write_preserves_status;
    @(posedge clk) disable iff (!rst_n)
      (fw_we && fw_addr == A_MASK && ev == '0) |=>
        (sts == $past(sts));
  endproperty
  a_mask_write_preserves_status: assert property (p_mask_write_preserves_status);

  // THE INTERRUPT IS DERIVED. No third copy of the same fact.
  property p_irq_is_derived;
    @(posedge clk) disable iff (!rst_n) irq == |(sts & msk);
  endproperty
  a_irq_is_derived: assert property (p_irq_is_derived);

  // A BIT IS NEVER CLEARED WITHOUT BEING ASKED. The commonest silent
  // integration bug is a status bit cleared by something other than
  // firmware's write to it.
  property p_no_spontaneous_clear;
    @(posedge clk) disable iff (!rst_n)
      ##1 (($past(sts) & ~sts) != '0) |->
          (($past(sts) & ~sts) == ($past(fw_clr) & ~$past(ev)));
  endproperty
  a_no_spontaneous_clear: assert property (p_no_spontaneous_clear);

  // LOSS IS RECORDED. A multiplicity a sticky bit cannot represent is
  // counted rather than silently discarded.
  property p_loss_counted;
    @(posedge clk) disable iff (!rst_n)
      ##1 c_lost == $past(c_lost) + 16'($countones($past(coincide)));
  endproperty
  a_loss_counted: assert property (p_loss_counted);

  c_collide:   cover property (@(posedge clk) |(ev & fw_clr & sts));
  c_masked_ev: cover property (@(posedge clk) |(ev & ~msk));
  c_loss:      cover property (@(posedge clk) |coincide);
  c_all_set:   cover property (@(posedge clk) &sts);
`endif

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  usb_irq_status (VHDL-2008) -- the same interrupt status register.
--  Identical contract to the Verilog and SystemVerilog versions: same
--  ports, same ordering decision, same reset, same latency.
--
--  The ordering decision -- clear applied to the OLD value, event set
--  afterwards -- reads the same in all three languages, and no language
--  will tell you which way round it belongs. That is the point of the
--  chapter this specimen serves: the review classes a type system CAN
--  remove are real and worth having, and this is not one of them.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity usb_irq_status is
  generic (NEV : natural := 6);
  port (
    clk      : in  std_logic;
    rst_n    : in  std_logic;
    ev       : in  std_logic_vector(NEV - 1 downto 0);
    fw_we    : in  std_logic;
    fw_addr  : in  unsigned(1 downto 0);
    fw_wdata : in  std_logic_vector(15 downto 0);
    fw_rdata : out std_logic_vector(15 downto 0);
    irq      : out std_logic;
    n_lost   : out unsigned(15 downto 0)
  );
end entity usb_irq_status;

architecture rtl of usb_irq_status is
  constant A_STATUS : unsigned(1 downto 0) := "00";
  constant A_MASK   : unsigned(1 downto 0) := "01";
  constant A_PEND   : unsigned(1 downto 0) := "10";
  constant A_LOST   : unsigned(1 downto 0) := "11";

  signal sts, msk  : std_logic_vector(NEV - 1 downto 0) := (others => '0');
  signal c_lost    : unsigned(15 downto 0) := (others => '0');
  signal fw_clr    : std_logic_vector(NEV - 1 downto 0);
  signal coincide  : std_logic_vector(NEV - 1 downto 0);
  signal lost_add  : natural range 0 to NEV;

  function popcount (v : std_logic_vector) return natural is
    variable n : natural := 0;
  begin
    for i in v'range loop
      if v(i) = '1' then n := n + 1; end if;
    end loop;
    return n;
  end function popcount;
begin

  fw_clr <= fw_wdata(NEV - 1 downto 0)
            when (fw_we = '1' and fw_addr = A_STATUS)
            else (others => '0');

  -- An event onto a bit that is already set AND is not being cleared
  -- this cycle is a multiplicity the sticky bit cannot represent.
  coincide <= ev and sts and (not fw_clr);
  lost_add <= popcount(coincide);

  seq : process (clk, rst_n)
  begin
    if rst_n = '0' then
      sts    <= (others => '0');
      msk    <= (others => '0');
      c_lost <= (others => '0');
    elsif rising_edge(clk) then
      -- The clear applies to the OLD value; the event is set after it,
      -- so a set and a clear of the same bit in the same cycle leave
      -- the bit SET. Reversing the two makes the clear win and loses
      -- the event silently.
      sts <= (sts and (not fw_clr)) or ev;

      -- A write to MASK changes the mask and nothing else.
      if fw_we = '1' and fw_addr = A_MASK then
        msk <= fw_wdata(NEV - 1 downto 0);
      end if;

      c_lost <= c_lost + to_unsigned(lost_add, 16);
    end if;
  end process seq;

  irq <= '1' when (sts and msk) /= (sts'range => '0') else '0';

  rd : process (fw_addr, sts, msk, c_lost)
    variable v : std_logic_vector(15 downto 0);
  begin
    v := (others => '0');
    case fw_addr is
      when A_STATUS => v(NEV - 1 downto 0) := sts;
      when A_MASK   => v(NEV - 1 downto 0) := msk;
      when A_PEND   => v(NEV - 1 downto 0) := sts and msk;
      when others   => v := std_logic_vector(c_lost);
    end case;
    fw_rdata <= v;
  end process rd;

  n_lost <= c_lost;

end architecture rtl;

11. The Testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  tb_usb_irq_status -- Verilog-2005 testbench for usb_irq_status.
//
//  PHASES
//    1 EXHAUSTIVE  every (status bit, event, firmware action) triple:
//                  6 bits x 2 event values x 4 actions x 2 mask values
//                  = 96 combinations, each entered from a built and
//                  PROVED state
//    2 COLLISION   the set/clear race, swept across all six bits and
//                  both directions
//    3 SCENARIO    the named integration traps, one each
//    4 RANDOM      supplementary, audited
// =====================================================================
`timescale 1ns/1ps

module tb_usb_irq_status;

  localparam integer NEV = 6;

  reg         clk = 1'b0;
  reg         rst_n;
  reg  [NEV-1:0] ev;
  reg         fw_we;
  reg  [1:0]  fw_addr;
  reg  [15:0] fw_wdata;
  wire [15:0] fw_rdata;
  wire        irq;
  wire [15:0] n_lost;

  usb_irq_status #(.NEV(NEV)) dut (
    .clk(clk), .rst_n(rst_n), .ev(ev),
    .fw_we(fw_we), .fw_addr(fw_addr), .fw_wdata(fw_wdata),
    .fw_rdata(fw_rdata), .irq(irq), .n_lost(n_lost)
  );

  always #5 clk = ~clk;

  // ---- the independent reference model ----
  reg [NEV-1:0] rm_sts, rm_msk;
  reg [15:0]    rm_lost;

  integer chk_dir, chk_rnd, err, in_random;
  integer m_set, m_clr, m_collide, m_coincide, m_masked_ev, m_irq,
          m_setupfail;
  integer b, e, a, mk, i, j;

  // A one-hot vector of the declared width. `1 << b` is an unsized
  // expression and cannot be concatenated, which Icarus says plainly
  // and many tools do not.
  function [NEV-1:0] bit_of;
    input integer n;
    begin bit_of = {{(NEV-1){1'b0}}, 1'b1} << n; end
  endfunction

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

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

  task ref_step;
    reg [NEV-1:0] clr, coin;
    integer k;
    begin
      if (!rst_n) begin
        rm_sts = 0; rm_msk = 0; rm_lost = 0;
      end else begin
        clr  = (fw_we && fw_addr == 2'd0) ? fw_wdata[NEV-1:0] : {NEV{1'b0}};
        // An event onto a bit that is already set AND is not being
        // cleared this cycle is a multiplicity the sticky bit cannot
        // represent. If firmware IS clearing it, nothing is lost: the
        // event simply sets the bit again.
        coin = ev & rm_sts & ~clr;
        for (k = 0; k < NEV; k = k + 1)
          if (coin[k]) rm_lost = rm_lost + 1;
        // The clear applies to the OLD value; the event is set after.
        rm_sts = (rm_sts & ~clr) | ev;
        if (fw_we && fw_addr == 2'd1) rm_msk = fw_wdata[NEV-1:0];
        // tallies
        if (|ev)            m_set      = m_set + 1;
        if (|clr)           m_clr      = m_clr + 1;
        if (|(ev & clr))    m_collide  = m_collide + 1;
        if (|coin)          m_coincide = m_coincide + 1;
        if (|(ev & ~rm_msk)) m_masked_ev = m_masked_ev + 1;
      end
    end
  endtask

  task cmp;
    begin
      fw_addr = 2'd0; #1;
      ck("STATUS", {16'd0, fw_rdata}, {26'd0, rm_sts});
      fw_addr = 2'd1; #1;
      ck("MASK",   {16'd0, fw_rdata}, {26'd0, rm_msk});
      fw_addr = 2'd2; #1;
      ck("PEND",   {16'd0, fw_rdata}, {26'd0, (rm_sts & rm_msk)});
      fw_addr = 2'd3; #1;
      ck("LOST",   {16'd0, fw_rdata}, {16'd0, rm_lost});
      ck("irq",    {31'd0, irq},      {31'd0, (|(rm_sts & rm_msk))});
      ck("n_lost", {16'd0, n_lost},   {16'd0, rm_lost});
      if (|(rm_sts & rm_msk)) m_irq = m_irq + 1;
      fw_addr = 2'd0;
    end
  endtask

  task step; begin
    #1;
    @(posedge clk);
    ref_step;
    #1;
    cmp;
    ev = 0; fw_we = 0; fw_wdata = 0;
  end endtask

  task idle; begin step; end endtask

  task hard_reset; begin
    rst_n = 0; ev = 0; fw_we = 0; fw_addr = 0; fw_wdata = 0;
    repeat (3) begin @(posedge clk); ref_step; end
    #1; rst_n = 1;
    @(posedge clk); ref_step; #1; cmp;
  end endtask

  task pulse;   input [NEV-1:0] e; begin ev = e; step; end endtask
  task wr_mask; input [NEV-1:0] m;
    begin fw_we = 1; fw_addr = 2'd1; fw_wdata = {10'd0, m}; step; end
  endtask
  task wr_clear; input [NEV-1:0] c;
    begin fw_we = 1; fw_addr = 2'd0; fw_wdata = {10'd0, c}; step; end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 1 -- the exhaustive sweep.
  //
  //  DENOMINATOR, derived:
  //    which bit          six events, and they are independent          6
  //    that bit's state   already set, or clear                         2
  //    firmware action    nothing / clear this bit / write mask /
  //                       clear a DIFFERENT bit                         4
  //    mask               this bit masked, or not                       2
  //    -----------------------------------------------------------------
  //                                              6 x 2 x 4 x 2 =       96
  //
  //  The six bits are swept rather than collapsed because "the bits are
  //  independent" is a CLAIM about the design, and a per-bit sweep is
  //  what turns it into a measurement. A design with a shared clear or a
  //  mis-indexed mask passes a one-bit test.
  // -----------------------------------------------------------------
  task phase_sweep;
    reg [15:0] rd;
    begin
      for (b = 0; b < NEV; b = b + 1)
      for (e = 0; e < 2; e = e + 1)
      for (a = 0; a < 4; a = a + 1)
      for (mk = 0; mk < 2; mk = mk + 1) begin
        hard_reset;
        wr_mask(mk ? bit_of(b) : {NEV{1'b0}});
        if (e) begin
          pulse(bit_of(b));
          // prove the state was built
          fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
          bump;
          if (rd[b] !== 1'b1) begin
            err = err + 1; m_setupfail = m_setupfail + 1;
            $display("  ** setup: bit %0d did not set", b);
          end
        end
        case (a)
          0: idle;
          1: wr_clear(bit_of(b));
          2: wr_mask({NEV{1'b1}});
          3: wr_clear(bit_of((b + 1) % NEV));
        endcase
        idle;
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 2 -- the collision, swept over every bit.
  //  An event and firmware's clear of the SAME bit, in the same cycle.
  //  The event is newer than the decision to clear it, so it wins.
  // -----------------------------------------------------------------
  task phase_collision;
    reg [15:0] rd;
    begin
      for (b = 0; b < NEV; b = b + 1) begin
        // the bit is set, firmware clears it, and it fires again at the
        // same instant
        hard_reset; wr_mask({NEV{1'b1}});
        pulse(bit_of(b));
        ev = bit_of(b); fw_we = 1; fw_addr = 2'd0;
        fw_wdata = {10'd0, bit_of(b)};
        step;
        fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
        ck("collision: the set wins", {31'd0, rd[b]}, 32'd1);
        ck("collision: irq stays up", {31'd0, irq},   32'd1);

        // and the same clear with NO event is an ordinary clear
        hard_reset; wr_mask({NEV{1'b1}});
        pulse(bit_of(b));
        wr_clear(bit_of(b));
        fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
        ck("clear alone works", {31'd0, rd[b]}, 32'd0);
        ck("irq drops",         {31'd0, irq},   32'd0);
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 3 -- the named integration traps.
  // -----------------------------------------------------------------
  task phase_traps;
    reg [15:0] rd;
    begin
      // T1 masking HIDES, it does not discard. A driver that masks
      //    during a critical section must not lose what happened in it.
      hard_reset;
      wr_mask(6'b000000);
      pulse(6'b000010);
      ck("T1 masked: no interrupt", {31'd0, irq}, 32'd0);
      fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
      ck("T1 but the status is set", {31'd0, rd[1]}, 32'd1);
      wr_mask(6'b111111);
      ck("T1 unmasking reveals it", {31'd0, irq}, 32'd1);

      // T2 a write to MASK must not disturb STATUS.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b101010);
      wr_mask(6'b000001);
      fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
      ck("T2 status untouched by a mask write", {26'd0, rd[NEV-1:0]}, 32'b101010);

      // T3 a sticky bit cannot count. Two events before firmware
      //    services the first are one bit -- and the loss is recorded.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b000100);
      pulse(6'b000100);
      ck("T3 still one bit", {16'd0, n_lost}, 32'd1);
      pulse(6'b000100);
      ck("T3 and another",   {16'd0, n_lost}, 32'd2);

      // T4 clearing one bit leaves the others alone, and the interrupt
      //    stays asserted while any unmasked bit remains.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b010100);
      wr_clear(6'b000100);
      fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
      ck("T4 only that bit cleared", {26'd0, rd[NEV-1:0]}, 32'b010000);
      ck("T4 interrupt still up",    {31'd0, irq},          32'd1);
      wr_clear(6'b010000);
      ck("T4 now it drops", {31'd0, irq}, 32'd0);

      // T5 all six at once, then cleared in one write.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b111111);
      ck("T5 interrupt up", {31'd0, irq}, 32'd1);
      wr_clear(6'b111111);
      ck("T5 all cleared",  {31'd0, irq}, 32'd0);
      ck("T5 nothing lost", {16'd0, n_lost}, 32'd0);
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 4 -- random, audited.
  // -----------------------------------------------------------------
  task phase_random;
    integer r;
    begin
      in_random = 1;
      hard_reset; wr_mask(6'b111111);
      for (j = 0; j < 6000; j = j + 1) begin
        r = {$random} % 100;
        if (r < 40) begin
          pulse({$random} % 64);
        end else if (r < 62) begin
          wr_clear({$random} % 64);
        end else if (r < 72) begin
          wr_mask({$random} % 64);
        end else if (r < 88) begin
          // the collision, steered: an event and a clear of an
          // OVERLAPPING set of bits in the same cycle. Unsteered, two
          // random six-bit masks overlap often enough -- but the bit
          // that matters is one that is ALREADY SET, and that is rarer.
          ev = {$random} % 64;
          fw_we = 1; fw_addr = 2'd0; fw_wdata = {10'd0, ev};
          step;
        end else begin
          idle;
        end
      end
      in_random = 0;
    end
  endtask

  initial begin
    chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
    m_set=0; m_clr=0; m_collide=0; m_coincide=0; m_masked_ev=0; m_irq=0;
    m_setupfail=0;

    phase_sweep;
    $display("  phase 1 exhaustive  : %0d checks, %0d errors  (96 combinations)",
             chk_dir, err);
    phase_collision;
    $display("  phase 2 collision   : %0d checks, %0d errors  (%0d bits x 2)",
             chk_dir, err, NEV);
    phase_traps;
    $display("  phase 3 traps       : %0d checks, %0d errors", chk_dir, err);
    $display("  ---- DIRECTED-ONLY  : %0d checks, %0d errors ----", chk_dir, err);
    phase_random;

    $display("");
    $display("  measured reachability (all phases)");
    $display("    cycles with an event ... %0d", m_set);
    $display("    cycles with a clear .... %0d", m_clr);
    $display("    set+clear same bit ..... %0d", m_collide);
    $display("    event onto a set bit ... %0d", m_coincide);
    $display("    event while masked ..... %0d", m_masked_ev);
    $display("    cycles asserting irq ... %0d", m_irq);
    $display("    setup failures ......... %0d", m_setupfail);
    $display("");
    $display("  directed checks ........ %0d", chk_dir);
    $display("  random checks .......... %0d", chk_rnd);
    $display("  TOTAL checks ........... %0d", chk_dir + chk_rnd);
    $display("  ERRORS ................. %0d", err);
    if (err == 0) $display("  PASS"); else $display("  FAIL");
    $finish;
  end

endmodule

The exhaustive phase sweeps 96 combinations and the collision phase sweeps twelve. The second number is smaller and it is the one that matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    BASE, full bench                directed     0    total      0
    C-M1, full bench                directed    30    total 18,173
    C-M1, collision phase REMOVED   directed     0    total 18,143
    BASE, collision phase removed   directed     0    total      0

The 96-combination exhaustive sweep catches the ordering defect zero times. Its axes are which bit, that bit's prior state, the firmware action and the mask — four axes, correctly derived, genuinely exhausted, and simultaneity is not one of them. 30.2 §10 draws the general conclusion; this is where it was measured.

The same bench in the other two languages, presenting the same directed stimulus: 3,096 directed checks in each, identical to the digit.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  tb_usb_irq_status_sv -- SystemVerilog testbench for usb_irq_status_sv.
//
//  Phases 1-3 present the SAME directed stimulus as the Verilog bench,
//  so their directed check counts must agree to the digit.
//
//  PHASES
//    1 EXHAUSTIVE  every (status bit, event, firmware action) triple:
//                  6 bits x 2 event values x 4 actions x 2 mask values
//                  = 96 combinations, each entered from a built and
//                  PROVED state
//    2 COLLISION   the set/clear race, swept across all six bits and
//                  both directions
//    3 SCENARIO    the named integration traps, one each
//    4 RANDOM      supplementary, audited
// =====================================================================
`timescale 1ns/1ps

module tb_usb_irq_status_sv;

  localparam int NEV = 6;

  logic       clk = 1'b0;
  logic       rst_n;
  logic [NEV-1:0] ev;
  logic       fw_we;
  logic [1:0] fw_addr;
  logic [15:0] fw_wdata;
  wire [15:0] fw_rdata;
  wire        irq;
  wire [15:0] n_lost;

  usb_irq_status_sv #(.NEV(NEV)) dut (
    .clk(clk), .rst_n(rst_n), .ev(ev),
    .fw_we(fw_we), .fw_addr(fw_addr), .fw_wdata(fw_wdata),
    .fw_rdata(fw_rdata), .irq(irq), .n_lost(n_lost)
  );

  always #5 clk = ~clk;

  // ---- the independent reference model ----
  logic [NEV-1:0] rm_sts, rm_msk;
  logic [15:0] rm_lost;

  int  chk_dir, chk_rnd, err;
  bit  in_random;
  int  m_set, m_clr, m_collide, m_coincide, m_masked_ev, m_irq,
       m_setupfail;
  int  b, e, a, mk, i, j;

  // A one-hot vector of the declared width. `1 << b` is an unsized
  // expression and cannot be concatenated, which Icarus says plainly
  // and many tools do not.
  function logic [NEV-1:0] bit_of(int n);
    return NEV'(1) << n;
  endfunction

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

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

  task ref_step;
    logic [NEV-1:0] clr, coin;
    int k;
    begin
      if (!rst_n) begin
        rm_sts = 0; rm_msk = 0; rm_lost = 0;
      end else begin
        clr  = (fw_we && fw_addr == 2'd0) ? fw_wdata[NEV-1:0] : {NEV{1'b0}};
        // An event onto a bit that is already set AND is not being
        // cleared this cycle is a multiplicity the sticky bit cannot
        // represent. If firmware IS clearing it, nothing is lost: the
        // event simply sets the bit again.
        coin = ev & rm_sts & ~clr;
        for (k = 0; k < NEV; k = k + 1)
          if (coin[k]) rm_lost = rm_lost + 1;
        // The clear applies to the OLD value; the event is set after.
        rm_sts = (rm_sts & ~clr) | ev;
        if (fw_we && fw_addr == 2'd1) rm_msk = fw_wdata[NEV-1:0];
        // tallies
        if (|ev)            m_set      = m_set + 1;
        if (|clr)           m_clr      = m_clr + 1;
        if (|(ev & clr))    m_collide  = m_collide + 1;
        if (|coin)          m_coincide = m_coincide + 1;
        if (|(ev & ~rm_msk)) m_masked_ev = m_masked_ev + 1;
      end
    end
  endtask

  task cmp;
    begin
      fw_addr = 2'd0; #1;
      ck("STATUS", {16'd0, fw_rdata}, {26'd0, rm_sts});
      fw_addr = 2'd1; #1;
      ck("MASK",   {16'd0, fw_rdata}, {26'd0, rm_msk});
      fw_addr = 2'd2; #1;
      ck("PEND",   {16'd0, fw_rdata}, {26'd0, (rm_sts & rm_msk)});
      fw_addr = 2'd3; #1;
      ck("LOST",   {16'd0, fw_rdata}, {16'd0, rm_lost});
      ck("irq",    {31'd0, irq},      {31'd0, (|(rm_sts & rm_msk))});
      ck("n_lost", {16'd0, n_lost},   {16'd0, rm_lost});
      if (|(rm_sts & rm_msk)) m_irq = m_irq + 1;
      fw_addr = 2'd0;
    end
  endtask

  task step; begin
    #1;
    @(posedge clk);
    ref_step;
    #1;
    cmp;
    ev = 0; fw_we = 0; fw_wdata = 0;
  end endtask

  task idle; step; endtask

  task hard_reset; begin
    rst_n = 0; ev = 0; fw_we = 0; fw_addr = 0; fw_wdata = 0;
    repeat (3) begin @(posedge clk); ref_step; end
    #1; rst_n = 1;
    @(posedge clk); ref_step; #1; cmp;
  end endtask

  task pulse(logic [NEV-1:0] e); ev = e; step; endtask
  task wr_mask(logic [NEV-1:0] m);
    fw_we = 1; fw_addr = 2'd1; fw_wdata = {10'd0, m}; step;
  endtask
  task wr_clear(logic [NEV-1:0] c);
    fw_we = 1; fw_addr = 2'd0; fw_wdata = {10'd0, c}; step;
  endtask

  // -----------------------------------------------------------------
  //  PHASE 1 -- the exhaustive sweep.
  //
  //  DENOMINATOR, derived:
  //    which bit          six events, and they are independent          6
  //    that bit's state   already set, or clear                         2
  //    firmware action    nothing / clear this bit / write mask /
  //                       clear a DIFFERENT bit                         4
  //    mask               this bit masked, or not                       2
  //    -----------------------------------------------------------------
  //                                              6 x 2 x 4 x 2 =       96
  //
  //  The six bits are swept rather than collapsed because "the bits are
  //  independent" is a CLAIM about the design, and a per-bit sweep is
  //  what turns it into a measurement. A design with a shared clear or a
  //  mis-indexed mask passes a one-bit test.
  // -----------------------------------------------------------------
  task phase_sweep;
    logic [15:0] rd;
    begin
      for (b = 0; b < NEV; b = b + 1)
      for (e = 0; e < 2; e = e + 1)
      for (a = 0; a < 4; a = a + 1)
      for (mk = 0; mk < 2; mk = mk + 1) begin
        hard_reset;
        wr_mask(mk ? bit_of(b) : {NEV{1'b0}});
        if (e) begin
          pulse(bit_of(b));
          // prove the state was built
          fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
          bump;
          if (rd[b] !== 1'b1) begin
            err = err + 1; m_setupfail = m_setupfail + 1;
            $display("  ** setup: bit %0d did not set", b);
          end
        end
        case (a)
          0: idle;
          1: wr_clear(bit_of(b));
          2: wr_mask({NEV{1'b1}});
          3: wr_clear(bit_of((b + 1) % NEV));
        endcase
        idle;
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 2 -- the collision, swept over every bit.
  //  An event and firmware's clear of the SAME bit, in the same cycle.
  //  The event is newer than the decision to clear it, so it wins.
  // -----------------------------------------------------------------
  task phase_collision;
    logic [15:0] rd;
    begin
      for (b = 0; b < NEV; b = b + 1) begin
        // the bit is set, firmware clears it, and it fires again at the
        // same instant
        hard_reset; wr_mask({NEV{1'b1}});
        pulse(bit_of(b));
        ev = bit_of(b); fw_we = 1; fw_addr = 2'd0;
        fw_wdata = {10'd0, bit_of(b)};
        step;
        fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
        ck("collision: the set wins", {31'd0, rd[b]}, 32'd1);
        ck("collision: irq stays up", {31'd0, irq},   32'd1);

        // and the same clear with NO event is an ordinary clear
        hard_reset; wr_mask({NEV{1'b1}});
        pulse(bit_of(b));
        wr_clear(bit_of(b));
        fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
        ck("clear alone works", {31'd0, rd[b]}, 32'd0);
        ck("irq drops",         {31'd0, irq},   32'd0);
      end
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 3 -- the named integration traps.
  // -----------------------------------------------------------------
  task phase_traps;
    logic [15:0] rd;
    begin
      // T1 masking HIDES, it does not discard. A driver that masks
      //    during a critical section must not lose what happened in it.
      hard_reset;
      wr_mask(6'b000000);
      pulse(6'b000010);
      ck("T1 masked: no interrupt", {31'd0, irq}, 32'd0);
      fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
      ck("T1 but the status is set", {31'd0, rd[1]}, 32'd1);
      wr_mask(6'b111111);
      ck("T1 unmasking reveals it", {31'd0, irq}, 32'd1);

      // T2 a write to MASK must not disturb STATUS.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b101010);
      wr_mask(6'b000001);
      fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
      ck("T2 status untouched by a mask write", {26'd0, rd[NEV-1:0]}, 32'b101010);

      // T3 a sticky bit cannot count. Two events before firmware
      //    services the first are one bit -- and the loss is recorded.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b000100);
      pulse(6'b000100);
      ck("T3 still one bit", {16'd0, n_lost}, 32'd1);
      pulse(6'b000100);
      ck("T3 and another",   {16'd0, n_lost}, 32'd2);

      // T4 clearing one bit leaves the others alone, and the interrupt
      //    stays asserted while any unmasked bit remains.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b010100);
      wr_clear(6'b000100);
      fw_addr = 2'd0; #1; rd = fw_rdata; fw_addr = 2'd0;
      ck("T4 only that bit cleared", {26'd0, rd[NEV-1:0]}, 32'b010000);
      ck("T4 interrupt still up",    {31'd0, irq},          32'd1);
      wr_clear(6'b010000);
      ck("T4 now it drops", {31'd0, irq}, 32'd0);

      // T5 all six at once, then cleared in one write.
      hard_reset; wr_mask(6'b111111);
      pulse(6'b111111);
      ck("T5 interrupt up", {31'd0, irq}, 32'd1);
      wr_clear(6'b111111);
      ck("T5 all cleared",  {31'd0, irq}, 32'd0);
      ck("T5 nothing lost", {16'd0, n_lost}, 32'd0);
    end
  endtask

  // -----------------------------------------------------------------
  //  PHASE 4 -- random, audited.
  // -----------------------------------------------------------------
  task phase_random;
    int r;
    begin
      in_random = 1;
      hard_reset; wr_mask(6'b111111);
      for (j = 0; j < 6000; j = j + 1) begin
        r = $urandom_range(99);
        if (r < 40) begin
          pulse(NEV'($urandom_range(63)));
        end else if (r < 62) begin
          wr_clear(NEV'($urandom_range(63)));
        end else if (r < 72) begin
          wr_mask(NEV'($urandom_range(63)));
        end else if (r < 88) begin
          // the collision, steered: an event and a clear of an
          // OVERLAPPING set of bits in the same cycle. Unsteered, two
          // random six-bit masks overlap often enough -- but the bit
          // that matters is one that is ALREADY SET, and that is rarer.
          ev = NEV'($urandom_range(63));
          fw_we = 1; fw_addr = 2'd0; fw_wdata = {10'd0, ev};
          step;
        end else begin
          idle;
        end
      end
      in_random = 0;
    end
  endtask

  initial begin
    chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
    m_set=0; m_clr=0; m_collide=0; m_coincide=0; m_masked_ev=0; m_irq=0;
    m_setupfail=0;

    phase_sweep;
    $display("  phase 1 exhaustive  : %0d checks, %0d errors  (96 combinations)",
             chk_dir, err);
    phase_collision;
    $display("  phase 2 collision   : %0d checks, %0d errors  (%0d bits x 2)",
             chk_dir, err, NEV);
    phase_traps;
    $display("  phase 3 traps       : %0d checks, %0d errors", chk_dir, err);
    $display("  ---- DIRECTED-ONLY  : %0d checks, %0d errors ----", chk_dir, err);
    phase_random;

    $display("");
    $display("  measured reachability (all phases)");
    $display("    cycles with an event ... %0d", m_set);
    $display("    cycles with a clear .... %0d", m_clr);
    $display("    set+clear same bit ..... %0d", m_collide);
    $display("    event onto a set bit ... %0d", m_coincide);
    $display("    event while masked ..... %0d", m_masked_ev);
    $display("    cycles asserting irq ... %0d", m_irq);
    $display("    setup failures ......... %0d", m_setupfail);
    $display("");
    $display("  directed checks ........ %0d", chk_dir);
    $display("  random checks .......... %0d", chk_rnd);
    $display("  TOTAL checks ........... %0d", chk_dir + chk_rnd);
    $display("  ERRORS ................. %0d", err);
    if (err == 0) $display("  PASS"); else $display("  FAIL");
    $finish;
  end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  tb_usb_irq_status -- VHDL-2008 testbench for usb_irq_status.
--  Phases 1-3 present the SAME directed stimulus as the Verilog and
--  SystemVerilog benches, so their directed counts must agree.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

entity tb_usb_irq_status is
end entity tb_usb_irq_status;

architecture sim of tb_usb_irq_status is
  constant NEV  : natural := 6;
  constant HALF : time    := 10 ns;

  signal clk      : std_logic := '0';
  signal rst_n    : std_logic := '0';
  signal ev       : std_logic_vector(NEV-1 downto 0) := (others => '0');
  signal fw_we    : std_logic := '0';
  signal fw_addr  : unsigned(1 downto 0) := "00";
  signal fw_wdata : std_logic_vector(15 downto 0) := (others => '0');
  signal fw_rdata : std_logic_vector(15 downto 0);
  signal irq      : std_logic;
  signal n_lost   : unsigned(15 downto 0);
  signal done_flag : boolean := false;

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

  function bit_of (n : natural) return std_logic_vector is
    variable v : std_logic_vector(NEV-1 downto 0) := (others => '0');
  begin
    v(n) := '1';
    return v;
  end function bit_of;
begin

  dut : entity work.usb_irq_status
    generic map (NEV => NEV)
    port map (clk => clk, rst_n => rst_n, ev => ev,
              fw_we => fw_we, fw_addr => fw_addr, fw_wdata => fw_wdata,
              fw_rdata => fw_rdata, irq => irq, n_lost => n_lost);

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

  stim : process
    variable chk_dir, chk_rnd, errs, shown : natural := 0;
    variable in_random : boolean := false;
    variable rm_sts, rm_msk : std_logic_vector(NEV-1 downto 0) := (others => '0');
    variable rm_lost : natural := 0;
    variable m_set, m_clr, m_collide, m_coincide, m_masked_ev, m_irq : natural := 0;
    variable m_setupfail : natural := 0;
    variable seed1 : positive := 190_337;
    variable seed2 : positive := 77_003;

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

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

    procedure ref_step is
      variable clr, coin : std_logic_vector(NEV-1 downto 0);
      variable zero : std_logic_vector(NEV-1 downto 0) := (others => '0');
    begin
      if rst_n = '0' then
        rm_sts := (others => '0'); rm_msk := (others => '0'); rm_lost := 0;
      else
        if fw_we = '1' and fw_addr = "00" then
          clr := fw_wdata(NEV-1 downto 0);
        else
          clr := (others => '0');
        end if;
        coin := ev and rm_sts and (not clr);
        for k in 0 to NEV-1 loop
          if coin(k) = '1' then rm_lost := rm_lost + 1; end if;
        end loop;
        rm_sts := (rm_sts and (not clr)) or ev;
        if fw_we = '1' and fw_addr = "01" then
          rm_msk := fw_wdata(NEV-1 downto 0);
        end if;
        if ev  /= zero then m_set := m_set + 1; end if;
        if clr /= zero then m_clr := m_clr + 1; end if;
        if (ev and clr) /= zero then m_collide := m_collide + 1; end if;
        if coin /= zero then m_coincide := m_coincide + 1; end if;
        if (ev and (not rm_msk)) /= zero then m_masked_ev := m_masked_ev + 1; end if;
      end if;
    end procedure ref_step;

    procedure cmp is
      variable zero : std_logic_vector(NEV-1 downto 0) := (others => '0');
    begin
      fw_addr <= "00"; wait for 1 ns;
      ck("STATUS", to_integer(unsigned(fw_rdata)), to_integer(unsigned(rm_sts)));
      fw_addr <= "01"; wait for 1 ns;
      ck("MASK",   to_integer(unsigned(fw_rdata)), to_integer(unsigned(rm_msk)));
      fw_addr <= "10"; wait for 1 ns;
      ck("PEND",   to_integer(unsigned(fw_rdata)),
                   to_integer(unsigned(rm_sts and rm_msk)));
      fw_addr <= "11"; wait for 1 ns;
      ck("LOST",   to_integer(unsigned(fw_rdata)), rm_lost);
      if (rm_sts and rm_msk) /= zero then
        ck("irq", b2i(irq), 1);
        m_irq := m_irq + 1;
      else
        ck("irq", b2i(irq), 0);
      end if;
      ck("n_lost", to_integer(n_lost), rm_lost);
      fw_addr <= "00";
    end procedure cmp;

    procedure step is
    begin
      wait for 1 ns;
      wait until rising_edge(clk);
      ref_step;
      wait for 1 ns;
      cmp;
      ev <= (others => '0'); fw_we <= '0'; fw_wdata <= (others => '0');
    end procedure step;

    procedure idle is begin step; end procedure;

    procedure hard_reset is
    begin
      rst_n <= '0'; ev <= (others => '0'); fw_we <= '0';
      fw_addr <= "00"; fw_wdata <= (others => '0');
      for i in 0 to 2 loop wait until rising_edge(clk); ref_step; end loop;
      wait for 1 ns; rst_n <= '1';
      wait until rising_edge(clk); ref_step; wait for 1 ns; cmp;
    end procedure hard_reset;

    procedure pulse (e : std_logic_vector(NEV-1 downto 0)) is
    begin ev <= e; step; end procedure;

    procedure wr_mask (m : std_logic_vector(NEV-1 downto 0)) is
      variable d : std_logic_vector(15 downto 0) := (others => '0');
    begin
      d(NEV-1 downto 0) := m;
      fw_we <= '1'; fw_addr <= "01"; fw_wdata <= d; step;
    end procedure;

    procedure wr_clear (c : std_logic_vector(NEV-1 downto 0)) is
      variable d : std_logic_vector(15 downto 0) := (others => '0');
    begin
      d(NEV-1 downto 0) := c;
      fw_we <= '1'; fw_addr <= "00"; fw_wdata <= d; step;
    end procedure;

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

    variable rd   : integer;
    variable zero : std_logic_vector(NEV-1 downto 0) := (others => '0');
    variable allo : std_logic_vector(NEV-1 downto 0) := (others => '1');
    variable d16  : std_logic_vector(15 downto 0);
    variable r    : natural;
  begin
    -- ---- PHASE 1 : 6 x 2 x 4 x 2 = 96 ----
    for b in 0 to NEV-1 loop
      for e in 0 to 1 loop
        for a in 0 to 3 loop
          for mk in 0 to 1 loop
            hard_reset;
            if mk = 1 then wr_mask(bit_of(b)); else wr_mask(zero); end if;
            if e = 1 then
              pulse(bit_of(b));
              fw_addr <= "00"; wait for 1 ns;
              rd := to_integer(unsigned(fw_rdata)); fw_addr <= "00";
              bump;
              if (rd / (2**b)) mod 2 /= 1 then
                errs := errs + 1; m_setupfail := m_setupfail + 1;
                report "  ** setup: bit did not set" severity warning;
              end if;
            end if;
            case a is
              when 0 => idle;
              when 1 => wr_clear(bit_of(b));
              when 2 => wr_mask(allo);
              when others => wr_clear(bit_of((b + 1) mod NEV));
            end case;
            idle;
          end loop;
        end loop;
      end loop;
    end loop;
    report "  phase 1 exhaustive  : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors  (96 combinations)";

    -- ---- PHASE 2 : the collision, every bit ----
    for b in 0 to NEV-1 loop
      hard_reset; wr_mask(allo);
      pulse(bit_of(b));
      d16 := (others => '0'); d16(NEV-1 downto 0) := bit_of(b);
      ev <= bit_of(b); fw_we <= '1'; fw_addr <= "00"; fw_wdata <= d16;
      step;
      fw_addr <= "00"; wait for 1 ns;
      rd := to_integer(unsigned(fw_rdata)); fw_addr <= "00";
      ck("collision: the set wins", (rd / (2**b)) mod 2, 1);
      ck("collision: irq stays up", b2i(irq), 1);

      hard_reset; wr_mask(allo);
      pulse(bit_of(b));
      wr_clear(bit_of(b));
      fw_addr <= "00"; wait for 1 ns;
      rd := to_integer(unsigned(fw_rdata)); fw_addr <= "00";
      ck("clear alone works", (rd / (2**b)) mod 2, 0);
      ck("irq drops", b2i(irq), 0);
    end loop;
    report "  phase 2 collision   : " & integer'image(chk_dir) &
           " checks, " & integer'image(errs) & " errors  (6 bits x 2)";

    -- ---- PHASE 3 : the named traps ----
    hard_reset;
    wr_mask("000000");
    pulse("000010");
    ck("T1 masked: no interrupt", b2i(irq), 0);
    fw_addr <= "00"; wait for 1 ns;
    rd := to_integer(unsigned(fw_rdata)); fw_addr <= "00";
    ck("T1 but the status is set", (rd / 2) mod 2, 1);
    wr_mask("111111");
    ck("T1 unmasking reveals it", b2i(irq), 1);

    hard_reset; wr_mask("111111");
    pulse("101010");
    wr_mask("000001");
    fw_addr <= "00"; wait for 1 ns;
    rd := to_integer(unsigned(fw_rdata)); fw_addr <= "00";
    ck("T2 status untouched by a mask write", rd, 42);

    hard_reset; wr_mask("111111");
    pulse("000100");
    pulse("000100");
    ck("T3 still one bit", to_integer(n_lost), 1);
    pulse("000100");
    ck("T3 and another", to_integer(n_lost), 2);

    hard_reset; wr_mask("111111");
    pulse("010100");
    wr_clear("000100");
    fw_addr <= "00"; wait for 1 ns;
    rd := to_integer(unsigned(fw_rdata)); fw_addr <= "00";
    ck("T4 only that bit cleared", rd, 16);
    ck("T4 interrupt still up", b2i(irq), 1);
    wr_clear("010000");
    ck("T4 now it drops", b2i(irq), 0);

    hard_reset; wr_mask("111111");
    pulse("111111");
    ck("T5 interrupt up", b2i(irq), 1);
    wr_clear("111111");
    ck("T5 all cleared", b2i(irq), 0);
    ck("T5 nothing lost", to_integer(n_lost), 0);

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

    -- ---- PHASE 4 : random ----
    in_random := true;
    hard_reset; wr_mask(allo);
    for j in 0 to 5999 loop
      r := rnd(100);
      if r < 40 then
        pulse(std_logic_vector(to_unsigned(rnd(64), NEV)));
      elsif r < 62 then
        wr_clear(std_logic_vector(to_unsigned(rnd(64), NEV)));
      elsif r < 72 then
        wr_mask(std_logic_vector(to_unsigned(rnd(64), NEV)));
      elsif r < 88 then
        d16 := (others => '0');
        d16(NEV-1 downto 0) := std_logic_vector(to_unsigned(rnd(64), NEV));
        ev <= d16(NEV-1 downto 0);
        fw_we <= '1'; fw_addr <= "00"; fw_wdata <= d16;
        step;
      else
        idle;
      end if;
    end loop;
    in_random := false;

    report "  measured reachability (all phases)";
    report "    cycles with an event ... " & integer'image(m_set);
    report "    cycles with a clear .... " & integer'image(m_clr);
    report "    set+clear same bit ..... " & integer'image(m_collide);
    report "    event onto a set bit ... " & integer'image(m_coincide);
    report "    event while masked ..... " & integer'image(m_masked_ev);
    report "    cycles asserting irq ... " & integer'image(m_irq);
    report "    setup failures ......... " & integer'image(m_setupfail);
    report "  directed checks ........ " & integer'image(chk_dir);
    report "  random checks .......... " & integer'image(chk_rnd);
    report "  TOTAL checks ........... " & integer'image(chk_dir + chk_rnd);
    report "  ERRORS ................. " & integer'image(errs);
    if errs = 0 then report "  PASS"; else report "  FAIL" severity failure; end if;
    done_flag <= true;
    wait;
  end process stim;

end architecture sim;

12. Mutations, Mapped To Review Items

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    MUT    REVIEW QUESTION                                  V-DIR  SV-DIR  VH-DIR
    C-M1   does a hardware SET beat a firmware CLEAR?          30      30      30
    C-M2   does masking HIDE an event or DISCARD it?           78      78      78
    C-M3   is the interrupt masked at the right place?         51      51      51
    C-M4   is the loss counter counting the right thing?       12      12      12

BASE reads zero in all nine columns and every directed column is identical across the three languages.

C-M2 — a mask write that also clears status — scores 78 and is the one to internalise, because the failure it produces is the hardest kind to attribute. A driver masks interrupts to enter a critical section; every event that occurs inside the section is destroyed rather than deferred; the device appears to work except under the load that makes critical sections long. The bug is in hardware, the trigger is in software, and the symptom is in neither.

13. The Checklist

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  CLOCKS
       [ ] every clock has a named source, a frequency range and an owner
       [ ] for each: can it stop? when? who decides?
       [ ] for each that can stop: is the state PAUSED, LOST, or
           UNDEFINED -- and undefined is the finding
       [ ] anything that must respond while a clock is stopped is not in
           that domain

    2  RESETS
       [ ] a register x reset-type table exists: cleared / preserved /
           undefined, with a reason in every cell
       [ ] a USB bus reset clears the data state and the toggles, and NOT
           the endpoint configuration
       [ ] for every PRESERVED cell: does the writer know the reset
           happened, and how?
       [ ] software reset scope is written down somewhere other than the
           datasheet

    3  DOMAIN CROSSINGS
       [ ] every crossing is classified by WHAT crosses: level, pulse,
           event, incremental multi-bit, arbitrary multi-bit, stream
       [ ] the mechanism matches the classification
       [ ] no multi-bit value is synchronised bit by bit
       [ ] the CDC report has been read, not just generated
       [ ] everyone involved knows simulation cannot model metastability

    4  THE REGISTER INTERFACE
       [ ] a hardware SET beats a firmware CLEAR of the same bit, in one
           expression, and the ORDER inside it is deliberate
       [ ] masking hides; it does not discard
       [ ] a mask write has no other side effect
       [ ] the interrupt is derived from status and mask, with no state
       [ ] a loss counter exists for events a sticky bit cannot represent
       [ ] every bit is swept independently

    5  MEMORY AND OWNERSHIP
       [ ] one owner per buffer at every instant
       [ ] ownership transfer is ONE observable event
       [ ] "owned by nobody" is impossible, or is detected
       [ ] reset, error and cancellation each have an ownership answer
       [ ] if the memory is cacheable, the maintenance operation has an
           owner and it is firmware

    6  POWER
       [ ] the layers are listed and their state machines are separate
       [ ] resume detection is not in the domain suspend stops
       [ ] what survives each power state is written down
       [ ] firmware policy is a layer, and it is somebody's

    7  THE SEAM ITSELF
       [ ] the datasheet says what the hardware does, including the
           collisions
       [ ] firmware's assumptions have been read back to the RTL team
       [ ] every "must not happen" has a counter firmware can read

14. Exercises

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  THE CLOCK TABLE
       Write the clock table for a full-speed device controller with
       suspend support. Three clocks minimum. For each: source, can it
       stop, what state is paused and what is lost.

    2  THE RESET TABLE
       Take the 30.1 specimen's registers and build the register x
       reset-type table. Justify every PRESERVED cell by naming the agent
       that must know.

    3  CDC CLASSIFICATION
       Classify these five crossings and pick a mechanism for each: a
       suspend request; a frame counter; a completed-transfer byte count;
       an endpoint-halt command; a received data stream.

    4  VERILOG
       Add a second mask so that one set of events goes to the CPU and
       another goes to a power controller. What does irq become? What
       does the loss counter now mean?

    5  SYSTEMVERILOG
       Implement it. Does any type construct help with the second mask?
       Say honestly if the answer is no.

    6  VHDL
       Implement it, and declare the range of the loss increment now that
       there are two consumers.

    7  THE COLLISION AXIS
       Extend the exhaustive sweep so that simultaneity IS an axis. Derive
       the new denominator. Compare it to the twelve-case collision phase
       and say which you would keep if you could only have one.

    8  OWNERSHIP
       A transfer is cancelled by firmware while the controller has a
       descriptor owned. Write the sequence that returns ownership safely,
       and name the two ways it can go wrong.

    9  DEBUG PREP
       For each of the four mutations in section 12, write the single
       register read that would distinguish it from the other three in a
       lab.

15. The Interview Answer

"The USB controller IP passed its own verification. What do you check when you drop it into an SoC?"

Everything it assumed about the chip around it, because that is where integration bugs live — an assumption held by one team and not by the other, with neither team wrong about its own block.

I start with clocks, and specifically with a list: source, frequency, owner, and can it stop. A USB controller never has one clock. There is a 48 MHz domain derived from a crystal, the bus clock from the fabric, and often the PHY's. For each one that can stop I want the answer to "what happens to the state" to be paused or lost, not undefined, and undefined is the common answer. The USB example that catches people is suspend: the device must respond to resume signalling with its main clock stopped, so the resume detector cannot be in the domain that suspend stops. That is a clocking requirement generated by a protocol requirement three layers away.

Then resets, as a table rather than a sentence. Four different events — power on, USB bus reset, endpoint halt cleared, software reset — with four different scopes, and every register in exactly one category per event. The integration question that the block-level review does not ask is about the preserved cells: if the endpoint configuration survives a bus reset, which is correct, then firmware's picture of the device is now stale and firmware had no event for it. Somebody has to raise one.

Then domain crossings, classified by what is crossing rather than by the fact that something is. A stable level takes two flip-flops and that is the only case where two flip-flops is the answer. A one-cycle pulse needs a toggle or a handshake. A multi-bit value needs Gray coding or a stable bus plus a handshake, because two flops per bit can sample a combination the source never held. And I would say out loud that none of this is settled by simulation: a correct synchroniser and a broken one are identical in every simulator, so CDC is settled in review and by static analysis.

Then the register seam, which is where I would actually spend the time, because two independent agents write the same bits and one of them is software. The specific question is what happens when a hardware event and a firmware write-one-to-clear land on the same cycle. The event is newer than firmware's decision to clear, so the set must win — and it is one expression whose operand order is the whole decision. I have seen the wrong order written under a comment describing the right one, with the testbench's reference model copying the same expression and agreeing with it for forty thousand cycles.

And the thing people skip: masking must hide an event, not discard it. A driver that masks during a critical section and loses everything that happened inside it produces a device that works except under the load that makes critical sections long.

16. What Carries Forward

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE SEAMS
    o  clocks, resets, domain crossings, the register interface, memory
       ownership, power -- each has two owners and no shared document
    o  every clock needs source, owner, and "can it stop"; and if it can,
       the state is PAUSED, LOST or -- the finding -- UNDEFINED
    o  resume detection cannot be in the domain that suspend stops
    o  a reset table has a cell per register per reset TYPE, and the
       PRESERVED cells are the integration question: does the writer know?

    CROSSINGS
    o  classify by WHAT crosses -- level, pulse, event, incremental
       multi-bit, arbitrary multi-bit, stream -- because two flip-flops is
       the right answer for exactly one of the six
    o  two flops per bit on a multi-bit bus samples values that never
       existed
    o  RTL simulation cannot model metastability, so CDC is settled in
       review and by static analysis, not by a testbench

    THE REGISTER SEAM
    o  a hardware SET beats a firmware CLEAR, in ONE expression, and the
       operand ORDER is the entire decision -- all three languages spell
       both versions identically and no type system has an opinion
    o  masking HIDES; a mask write that clears status destroys exactly the
       events a critical section was protecting
    o  a sticky bit cannot count, so the lost multiplicity needs a counter
    o  sweep every bit: "the bits are independent" is a claim

    OWNERSHIP
    o  one owner per buffer per instant; transfer is ONE event
    o  "owned by nobody" is a hang, not a corruption, and reviews skip it
    o  reset, error and cancellation each need an ownership answer
    o  cacheable memory adds a maintenance step that belongs to firmware

    MEASURED HERE
    o  the ordering defect was in this module's own specimen, and its
       reference model contained the same wrong expression
    o  a 96-combination exhaustive sweep catches it ZERO times, because
       simultaneity was not one of its four axes

The next chapter assumes all of this has already failed, in a rack, eighteen months from now, and asks what to look at first.

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.