Skip to content
VLSI Mentor

USB · Module 30

RTL Review Checklist

A pre-tapeout RTL review is not a list of reminders — it is nine ordered questions, each with an invariant, the evidence that settles it, and the false confidence that hides it. Worked on a USB endpoint specimen with six planted findings, corrected in three languages.

Twenty-nine modules have built USB up from a differential pair to a bridge chip on a development board. This one turns that into a procedure: how a senior engineer sits down with somebody else's RTL and systematically finds what is wrong with it before the wafers are ordered.

1. What A Review Is For, And What It Is Not

A design review is not a second opinion on whether the code works. Simulation already answers that, and it answers it better than any human reading. A review asks a different question, and the difference is the whole of this module:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    SIMULATION ASKS   does this design do what the testbench expects?
    A REVIEW ASKS     is the testbench expecting the right things, and is
                      there a reachable situation nobody has expected yet?

Which means a review that consists of reading the code and nodding is worthless, and so is a checklist that says:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    [ ] check reset
    [ ] check the FSM
    [ ] check timing
    [ ] check assertions

Every item on that list is true and none of it is actionable. "Check reset" against what? A reviewer who does not already know what the answer should be cannot check anything.

2. The Order, And Why It Is Not Alphabetical

Review items have dependencies. Asking about boundary behaviour before you know which state is authoritative produces an answer about the wrong register. Asking about verification evidence first produces a discussion about the testbench instead of the design.

The order below is the one this chapter uses, and each step is answerable only because the steps above it have been answered.

The order of an RTL review

The ordered stages of an RTL review, read from top to bottom. First architecture and contract: what is this block for and what does it promise. Then authoritative state: which registers hold the truth. Then reset scope: what each kind of reset clears and what it must not. Then data movement: how values get from input to output. Then priority and collisions: what wins when two things happen at once. Then boundaries: zero, one, maximum and overflow. Then liveness: whether useful work can be starved. Then observability: what a field failure will leave behind. Finally verification evidence: whether the tests that pass are the right tests.Nine questions, in the order they can be answered1. Architecture and contractWhat is this block for, and what does it promise?What is this block for, and what does it promise?2. Authoritative stateWhich register holds the truth, and is there only one?Which register holds the truth, and is there only one?3. Reset scopeWhat does each reset clear, and what must it not?What does each reset clear, and what must it not?4. Data movementHow does a value get from an input to an output?How does a value get from an input to an output?5. Priority and collisionsWhat wins when two things happen in one cycle?What wins when two things happen in one cycle?6. BoundariesZero, one, maximum, one past maximum, empty, fullZero, one, maximum, one past maximum, empty, full7. LivenessCan work that is ready be starved forever?Can work that is ready be starved forever?8. ObservabilityWhat will a field failure leave behind to find?What will a field failure leave behind to find?9. Verification evidenceAre the tests that pass the right tests? See 30.2.Are the tests that pass the right tests? See 30.2.
Read downward. Each band is answerable only once the bands above it are settled: you cannot ask what happens at a boundary until you know which register holds the value, and you cannot judge the verification evidence until you know what the design was supposed to do. Reviews that start at the bottom — with the testbench, or with the lint report — produce findings about the evidence rather than about the design.

Two of those nine are worth defending, because they are the ones most often left off a review checklist entirely.

Liveness (7) is separate from everything above it because a design can be completely correct in every band from 1 to 6 and still never do anything useful. Nothing in a data-correctness review detects a starved requester: there is no wrong value to find. 29.6 measured exactly that — a design whose transmit path never ran passed 115,799 data checks.

Observability (8) is not a nicety. A review is partly an argument about what happens when the review was wrong, and a block that fails silently in the field converts a five-minute diagnosis into a week. One flip-flop is usually the difference.

3. The Specimen: What You Have Been Handed

Everything from here is worked on one block. It is a USB OUT endpoint's transfer controller — the layer above packet acceptance, which turns a sequence of accepted packets into one event firmware is told about.

It is a real shape. 29.5 built the layer below it, which decides whether a packet is accepted at all; this is the layer that decides when enough packets have arrived to constitute a transfer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    CONTRACT, as submitted
    ----------------------
    PURPOSE     accumulate accepted packets into a transfer, decide when
                the transfer is complete, honour the endpoint halt, and
                hand the completion to firmware

    COMPLETION  a transfer ends on a SHORT PACKET -- one strictly shorter
                than the endpoint maximum -- or when the accumulated byte
                count reaches the length firmware asked for

    INPUTS      clk, rst_n, usb_reset
                cfg_we, cfg_maxpkt, cfg_limit     firmware configuration
                pkt_valid, pkt_len                from the packet layer
                halt_set, halt_clr                from the control endpoint
                fw_ack                            firmware consumed it

    OUTPUTS     halted, ep_busy, xfer_done, xfer_bytes, short_pkt
                n_xfer, n_halt                    observation

The specimen, as a reviewer should sketch it before reading a line

A block diagram of the USB endpoint transfer controller under review. On the left, the packet layer offers a packet with a length, and firmware writes the endpoint configuration of maximum packet size and transfer limit. In the middle, a byte accumulator adds accepted packet lengths, and a completion rule compares against the maximum packet size and the limit. On the right, a single completion slot holds the byte count and the short-packet flag until firmware acknowledges it, and a halt bit is set and cleared by the control endpoint. The completion slot drives the busy output back to the packet layer and the done output to firmware.Packet layerpkt_valid, pkt_lenFirmware cfgmaxpkt, limitControl EPhalt_set, halt_clrAccumulatorbytes so farCompletionshort, or at limitOne slotbytes + short flagFirmwarereads, then ackslengthmaxpkttotalcompletedoneabandon12
Three sources that change state, one accumulator, one completion slot, and one consumer. Drawing this from the port list before reading the body is the cheapest thing a reviewer does, because it produces the questions rather than the answers. Note what the picture does not show: how many packets may be in flight toward the single slot, and what happens when the control endpoint asserts both of its inputs at once. Those two absences are findings F3 and F5.

Here is the module as submitted. It compiles, it lints clean, and it passes the testbench that came with it. Read it before the next section; the six findings are all visible from the source alone.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_ep_xfer -- AS SUBMITTED FOR REVIEW.
//
//  An OUT endpoint's TRANSFER controller: the layer above packet
//  acceptance. It accumulates accepted packets into a USB transfer,
//  decides when the transfer is complete, honours the endpoint halt
//  condition, and hands the completion to firmware.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL, and it is
//  DELIBERATELY DEFECTIVE. This is the version a reviewer receives.
//  It compiles, it lints clean, and it passes the testbench that was
//  submitted with it. Chapter 30.1 works through what is wrong with it;
//  chapter 30.2 works through why the testbench did not say so.
//
//  Do not copy this module. The reviewed version is in section 7.
// =====================================================================
module usb_ep_xfer_before (
  input  wire        clk,
  input  wire        rst_n,
  input  wire        usb_reset,      // one cycle, from the SE0 detector

  // ---- firmware configuration ----
  input  wire        cfg_we,
  input  wire [6:0]  cfg_maxpkt,     // 8, 16, 32 or 64 for full-speed bulk
  input  wire [15:0] cfg_limit,      // the transfer length firmware expects

  // ---- from the packet layer ----
  input  wire        pkt_valid,      // one cycle: a packet was accepted
  input  wire [6:0]  pkt_len,        // 0 .. maxpkt

  // ---- from the control endpoint ----
  input  wire        halt_set,       // SET_FEATURE(ENDPOINT_HALT)
  input  wire        halt_clr,       // CLEAR_FEATURE(ENDPOINT_HALT)

  // ---- to firmware ----
  input  wire        fw_ack,
  output wire        halted,
  output wire        ep_busy,
  output wire        xfer_done,
  output wire [15:0] xfer_bytes,
  output wire        short_pkt,

  output wire [15:0] n_xfer,
  output wire [15:0] n_halt
);

  reg [6:0]  maxpkt;
  reg [15:0] limit;
  reg [15:0] acc;
  reg        halt_r;
  reg        done_r;
  reg        short_r;
  reg [15:0] bytes_r;
  reg [15:0] c_xfer, c_halt;

  wire accept = pkt_valid && !halt_r && (pkt_len != 7'd0);

  wire [15:0] acc_next  = acc + {9'd0, pkt_len};
  wire        is_short  = (pkt_len < maxpkt);
  wire        hit_limit = (acc_next >= limit);
  wire        complete  = accept && (is_short || hit_limit);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      maxpkt  <= 7'd64;
      limit   <= 16'd0;
      acc     <= 16'd0;
      halt_r  <= 1'b0;
      done_r  <= 1'b0;
      short_r <= 1'b0;
      bytes_r <= 16'd0;
      c_xfer  <= 16'd0;
      c_halt  <= 16'd0;
    end else if (usb_reset) begin
      maxpkt  <= 7'd64;
      limit   <= 16'd0;
      acc     <= 16'd0;
      halt_r  <= 1'b0;
      done_r  <= 1'b0;
      short_r <= 1'b0;
      bytes_r <= 16'd0;
    end else begin
      if (cfg_we) begin
        maxpkt <= cfg_maxpkt;
        limit  <= cfg_limit;
      end

      if (halt_set) begin
        halt_r <= 1'b1;
        c_halt <= c_halt + 16'd1;
      end else if (halt_clr) begin
        halt_r <= 1'b0;
        acc    <= 16'd0;
      end

      if (accept) begin
        if (complete) begin
          bytes_r <= acc_next;
          short_r <= is_short;
          done_r  <= 1'b1;
          acc     <= 16'd0;
          c_xfer  <= c_xfer + 16'd1;
        end else begin
          acc <= acc_next;
        end
      end

      if (fw_ack) done_r <= 1'b0;
    end
  end

  assign halted     = halt_r;
  assign ep_busy    = (bytes_r != 16'd0);
  assign xfer_done  = done_r;
  assign xfer_bytes = bytes_r;
  assign short_pkt  = short_r;
  assign n_xfer     = c_xfer;
  assign n_halt     = c_halt;

endmodule

4. Question 2 — Authoritative State

Is this value stored because it must persist, or stored because it was convenient? And is there exactly one copy of each fact?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every architectural fact has ONE register that holds
                       it, and every other signal that mentions it is
                       derived from that register
    EVIDENCE           read every output assignment and ask what it is a
                       function of; anything that is a function of two
                       things that "should agree" is the finding
    FAILURE SIGNATURE  the two copies disagree for one cycle, or in one
                       corner, and the symptom is somewhere else entirely
    FALSE CONFIDENCE   "they are always the same" -- which is a claim about
                       every reachable state, and is what needs proving
    NEXT               if they can disagree, ask which one the rest of the
                       design believes

Applied to the specimen, one line fails immediately:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign ep_busy    = (bytes_r != 16'd0);

ep_busy means a completion is pending. The register that holds that fact is done_r. bytes_r is the byte count of the pending completion — a different fact that usually correlates with it.

They disagree in exactly one case: a transfer that completes with zero bytes. Which looks impossible until you notice finding F2 below, and then becomes an ordinary occurrence.

5. Question 3 — Reset Scope

What does each kind of reset clear, and — the half that gets left out — what must it not clear?

29.5 established that "reset" names at least four different events in a USB device. The review question is not "is reset connected" but "is the scope of each one right in both directions".

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          a USB bus reset returns the endpoint's DATA state to
                       default and leaves firmware's CONFIGURATION alone
    EVIDENCE           read the reset branch as a LIST and compare it to the
                       state list. Every register is in exactly one of three
                       categories: cleared by both, cleared by one, cleared
                       by neither -- and every one needs a reason.
    FAILURE SIGNATURE  too long: the device loses its configuration on every
                       host suspend/resume, and the failure is attributed to
                       the host
                       too short: stale state survives a reset the host
                       believes has cleaned everything, and the device goes
                       deaf after re-enumeration
    FALSE CONFIDENCE   "the reset list matches the declaration list" -- which
                       checks that nothing was forgotten, not that nothing
                       was added
    NEXT               ask who WROTE each surviving register, and whether
                       that agent knows the reset happened

In the specimen:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    end else if (usb_reset) begin
      maxpkt  <= 7'd64;
      limit   <= 16'd0;

maxpkt and limit were written by firmware in response to the host's SET_CONFIGURATION. A bus reset does not revoke that — and the device has no way to know it needs to rewrite them, because from firmware's point of view nothing happened. Finding F1, and it is a BLOCKER: the endpoint silently reverts to a 64-byte maximum and a zero-length limit, so the very next packet completes a transfer that should have continued.

6. Question 5 — Priority And Collisions

For every pair of inputs that can arrive in the same cycle: what wins, why, is that an architectural decision, and is it written down once?

This is the question that most distinguishes a review from a reading. Collisions are invisible in a linear read of the code because the code describes them with sequential-looking syntax that is not sequential.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every simultaneous pair resolves the way the
                       ARCHITECTURE requires, and the resolution appears in
                       exactly one place in the source
    EVIDENCE           enumerate the pairs -- there are not many -- and for
                       each one find the line that decides it
    FAILURE SIGNATURE  rare, load-dependent, and irreproducible on the bench
                       because the bench does one thing per cycle
    FALSE CONFIDENCE   "the if/else covers it" -- an if/else always produces
                       AN answer; the question is whether anybody chose it
    NEXT               if the priority is accidental, ask what the other
                       order would do, and whether anything tests it

The specimen has two pairs worth listing, and the review finds one defect and one near-miss:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    halt_set + halt_clr     BOTH can arrive together: the host can send
                            SET_FEATURE and CLEAR_FEATURE back to back and
                            the control endpoint can present them adjacently.
                            The submitted code writes:

                                if (halt_set) ... else if (halt_clr) ...

                            so SET wins. Nothing says that was chosen, and it
                            is the wrong way round: CLEAR_FEATURE is the
                            host's explicit recovery action, so a halt that
                            coincides with it is stale.   FINDING F5

    pkt_valid + fw_ack      can arrive together, and they touch the same
                            register from opposite directions. The submitted
                            code sets done_r in one branch and clears it in a
                            later statement, so the CLEAR wins -- which here
                            happens to be harmless, because the completion
                            being acknowledged is the one being replaced.
                            Harmless and undocumented is still a finding, at
                            LOW: the next person to touch it has no way to
                            know it was considered.

7. Question 6 — Boundaries

Zero, one, maximum, one past maximum, empty, full, and one either side of every comparison.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every comparison in the design has been evaluated at
                       both sides of its threshold, and every counted
                       quantity has been evaluated at zero and at its
                       maximum
    EVIDENCE           list the comparisons; there are usually fewer than
                       ten. For each, name the value that makes it flip.
    FAILURE SIGNATURE  size-dependent. Works for every transfer except the
                       ones that are an exact multiple of something.
    FALSE CONFIDENCE   "random testing covers it" -- uniform random over
                       0..64 spends 1.5% of its packets on the two values
                       that matter
    NEXT               if a boundary value is not reachable, ask why, and
                       whether the reason is a configuration constraint
                       nobody has written down

The specimen has three comparisons. Two are fine. The third is not a comparison at all — it is a guard that should not exist:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  wire accept = pkt_valid && !halt_r && (pkt_len != 7'd0);

A zero-length packet is a packet. It is the canonical way a bulk transfer whose length is an exact multiple of the maximum packet size is terminated: the host sends the data, then sends nothing, and the nothing is the message. Dropping it means such a transfer never completes.

Finding F2, BLOCKER. The failure signature is the giveaway: the device works perfectly except for transfers of exactly 64, 128, 192 bytes — so it fails on one file and not another, and the bug report says "large files hang sometimes".

8. Question 7 — Liveness

Can work that is ready to proceed be prevented from proceeding, forever or for an unbounded time?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every architectural resource that can be occupied is
                       eventually released, and every producer that is
                       blocked is told so rather than ignored
    EVIDENCE           a progress property with a DERIVED bound, plus a
                       scenario that creates the contention. Data checks
                       cannot supply this: a starved path corrupts nothing.
    FAILURE SIGNATURE  works on the bench, fails under sustained load, and
                       the change that exposed it is unrelated to the change
                       that caused it
    FALSE CONFIDENCE   "every byte that went in came out" -- true, and
                       compatible with half of them never going in
    NEXT               ask what the producer does when the consumer is not
                       ready, and whether "nothing" is one of the answers

The specimen holds exactly one completion at a time. So the question is: what happens when a second transfer completes before firmware has acknowledged the first?

The submitted code answers by overwriting it. bytes_r and short_r are replaced, done_r is already set and stays set, and firmware — which was about to read a 4-byte completion — reads a 6-byte one instead. The first transfer's length is gone, and nothing anywhere records that it existed.

Finding F3, HIGH. The fix is two parts and both matter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    BACKPRESSURE   ep_busy tells the packet layer not to offer. The contract
                   gains a sentence, and the layer below gains an obligation.
    A RECORD       a packet offered anyway is dropped and COUNTED. The
                   contract says that cannot happen, so a non-zero count is
                   a bug somewhere else -- which is exactly why it is a port
                   and not an assertion. An assertion is not present in the
                   field.

A second transfer completing before firmware has read the first

A waveform comparing the submitted and reviewed designs when a second transfer completes before firmware acknowledges the first. A four-byte packet completes a transfer at cycle one and the done and busy outputs assert with a byte count of four. At cycle three a six-byte packet arrives. In the submitted design the byte count changes to six, silently replacing the value firmware was about to read. In the reviewed design the byte count stays at four, the lost counter increments to one, and firmware acknowledges at cycle six releasing the slot.first transferfirst transferthe collisionthe collisionreleasedreleasedfirst completion: four bytesfirst completion: fourbytessubmitted: silently becomes sixsubmitted: silently becomessixfirmware acknowledgesfirmware acknowledgesclkpkt_validpkt_len--4--6------9--fw_ackxfer_doneep_busy (new)bytes (old)004466669bytes (new)004444449n_lost (new)000011111t0t1t2t3t4t5t6t7t8
The endpoint maximum is 16 bytes. Two transfers complete four cycles apart while firmware is busy. In the submitted design the second overwrites the first and the four-byte length is gone with no record. In the reviewed design the endpoint asserts busy, the packet layer must not offer, and a packet offered anyway is dropped and counted — so the completion firmware is about to read is still the one it was told about.

9. Question 8 — Observability

When this block fails in the field, eighteen months from now, in a customer's rack, what will it leave behind?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    INVARIANT          every contract violation BY ANOTHER BLOCK is
                       recorded, because a violation by somebody else is
                       the hardest kind of failure to attribute
    EVIDENCE           read the contract's "must not" sentences and check
                       that each one has a counter or a sticky bit
    FAILURE SIGNATURE  an escalation that takes a week and ends in "we
                       cannot reproduce it"
    FALSE CONFIDENCE   "there is an assertion for it" -- assertions are not
                       present in silicon
    NEXT               ask who reads the counter, and whether any firmware
                       does

The reviewed design adds n_lost. The submitted one has no equivalent, which means F3's data loss is not merely a bug but an invisible bug: firmware sees a plausible completion every time, the host sees a plausible transfer every time, and the only evidence is a byte count that is occasionally the wrong one.

One 16-bit counter. It costs less than the meeting that would be held about it.

10. The Findings, With Severity

Severity is a claim about consequence, not about how annoying the code is. The scale used here, and the reason each finding sits where it does:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    BLOCKER  can corrupt architectural state or violate the protocol. The
             design cannot tape out.
    HIGH     can lose data, deadlock or starve under a reachable condition.
    MEDIUM   reduces observability, or makes a correct behaviour depend on
             an assumption nobody has written down.
    LOW      maintainability, with no demonstrated functional consequence.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    ID  SEV      FINDING                                        REVIEW ITEM
    --  -------  ---------------------------------------------  -----------
    F1  BLOCKER  a USB bus reset clears maxpkt and limit, which  Q3 reset
                 firmware wrote and the host's reset does not
                 revoke. The endpoint silently reverts to a
                 64-byte maximum and a zero limit.

    F2  BLOCKER  a zero-length packet is not accepted, so a       Q6 boundary
                 transfer whose length is an exact multiple of
                 maxpkt never terminates.

    F3  HIGH     a completion is overwritten if firmware has not  Q7 liveness
                 acknowledged the previous one. No backpressure   Q8 observ.
                 and no record.

    F4  HIGH     clearing a halt abandons the accumulator but     Q2 state
                 leaves the pending completion and its byte
                 count behind, so the next transfer can report
                 the abandoned one's length.

    F5  MEDIUM   halt_set and halt_clr in the same cycle resolve  Q5 collision
                 by coding order rather than by decision, and
                 the order is the wrong one.

    F6  MEDIUM   ep_busy is derived from bytes_r rather than      Q2 state
                 from the authoritative done_r. Masked by F2;
                 becomes reachable when F2 is fixed.

11. The Reviewed Design (Verilog-2005)

Every change carries the [Fn] of the finding it answers, so the diff reads as a review record rather than as an edit.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_ep_xfer -- AFTER REVIEW.
//
//  The same hardware contract as the submitted version, with the six
//  findings of chapter 30.1 closed. Every change carries the [Fn] of
//  the finding it answers, so the diff reads as a review record.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL. It is not a
//  USB endpoint. There is no packet RAM, no serial interface engine, no
//  data toggle (29.5 owns that) and no IN direction. It is the TRANSFER
//  layer: the part that turns a sequence of accepted packets into one
//  event firmware is told about.
//
//  THE CONTRACT
//  ------------
//  PURPOSE     accumulate accepted packets into a transfer, decide when
//              the transfer is complete, honour the endpoint halt, and
//              hand the completion to firmware exactly once
//
//  COMPLETION  a transfer ends on a SHORT PACKET -- one strictly shorter
//              than the endpoint maximum, and a zero-length packet is
//              the canonical case -- or when the accumulated byte count
//              reaches the length firmware asked for. First one wins.
//
//  OWNERSHIP   a completion is held until fw_ack. While one is held the
//              endpoint asserts ep_busy and the packet layer must not
//              offer another packet. A packet offered anyway is a
//              contract violation BY THE LAYER BELOW: it is dropped and
//              counted, never allowed to overwrite unread data.
//
//  RESET       rst_n      everything, configuration included
//              usb_reset  the data state only. NOT maxpkt/limit, which
//                         firmware wrote and the host's reset does not
//                         revoke; NOT the counters, which are
//                         diagnostics rather than state.
//
//  PRIORITY    rst_n beats everything; usb_reset beats every input;
//              halt_clr beats halt_set. See the assignment for why.
//
//  ASSUMPTION  cfg_limit + cfg_maxpkt - 1 must fit in 16 bits, i.e.
//              cfg_limit <= 65472 at the largest full-speed bulk
//              maximum. NOTHING IN THIS MODULE CHECKS THAT. It is
//              recorded here because an undocumented constraint is the
//              seventh finding in 30.1 -- and it is not a defect, it is
//              a missing sentence.
// =====================================================================
module usb_ep_xfer (
  input  wire        clk,
  input  wire        rst_n,
  input  wire        usb_reset,

  input  wire        cfg_we,
  input  wire [6:0]  cfg_maxpkt,
  input  wire [15:0] cfg_limit,

  input  wire        pkt_valid,
  input  wire [6:0]  pkt_len,

  input  wire        halt_set,
  input  wire        halt_clr,

  input  wire        fw_ack,
  output wire        halted,
  output wire        ep_busy,
  output wire        xfer_done,
  output wire [15:0] xfer_bytes,
  output wire        short_pkt,

  output wire [15:0] n_xfer,
  output wire [15:0] n_halt,
  // [F3] A packet arrived while a completion was still pending. The
  // contract says the layer below must not do that, so a non-zero value
  // here is a bug SOMEWHERE ELSE -- which is exactly why it is a port
  // and not an assertion.
  output wire [15:0] n_lost
);

  reg [6:0]  maxpkt;
  reg [15:0] limit;
  reg [15:0] acc;
  reg        halt_r;
  reg        done_r;
  reg        short_r;
  reg [15:0] bytes_r;
  reg [15:0] c_xfer, c_halt, c_lost;

  // [F6] Busy IS the completion flag. Deriving it from bytes_r != 0 is
  // a second source of truth for one fact, and the two disagree exactly
  // when a transfer completes with zero bytes -- which, once [F2] is
  // fixed, is an ordinary transfer rather than an impossibility.
  // ONE name for "a completion is pending", and everything -- the
  // output port, the accept gate and the drop detector -- reads it.
  wire busy_i = done_r;
  assign ep_busy = busy_i;

  // [F2] A zero-length packet is a PACKET. It is how a transfer whose
  // length is an exact multiple of the maximum packet size is
  // terminated, and dropping it hangs that transfer forever.
  wire offered = pkt_valid && !halt_r;
  wire accept  = offered && !busy_i;
  wire dropped = offered &&  busy_i;

  wire [15:0] acc_next  = acc + {9'd0, pkt_len};
  wire        is_short  = (pkt_len < maxpkt);
  wire        hit_limit = (acc_next >= limit);
  wire        complete  = accept && (is_short || hit_limit);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      maxpkt  <= 7'd64;
      limit   <= 16'd0;
      acc     <= 16'd0;
      halt_r  <= 1'b0;
      done_r  <= 1'b0;
      short_r <= 1'b0;
      bytes_r <= 16'd0;
      c_xfer  <= 16'd0;
      c_halt  <= 16'd0;
      c_lost  <= 16'd0;
    end else if (usb_reset) begin
      // [F1] maxpkt and limit are absent from this list on purpose.
      acc     <= 16'd0;
      halt_r  <= 1'b0;
      done_r  <= 1'b0;
      short_r <= 1'b0;
      bytes_r <= 16'd0;
    end else begin
      if (cfg_we) begin
        maxpkt <= cfg_maxpkt;
        limit  <= cfg_limit;
      end

      // [F5] The collision is resolved once, in one place, and in the
      // direction the architecture requires: CLEAR_FEATURE is the host's
      // explicit recovery action, so a halt request arriving in the same
      // cycle is stale and loses. Written the other way round the
      // priority is whatever the author typed first, and nobody reading
      // it can tell that a decision was made.
      if (halt_clr) begin
        // [F4] Clearing a halt abandons the whole transfer, which means
        // the PENDING COMPLETION as well as the accumulator. Leaving
        // bytes_r and done_r behind lets the next transfer report the
        // abandoned one's length.
        halt_r  <= 1'b0;
        acc     <= 16'd0;
        done_r  <= 1'b0;
        short_r <= 1'b0;
        bytes_r <= 16'd0;
      end else if (halt_set) begin
        halt_r  <= 1'b1;
        c_halt  <= c_halt + 16'd1;
      end else begin
        if (accept) begin
          if (complete) begin
            bytes_r <= acc_next;
            short_r <= is_short;
            done_r  <= 1'b1;
            acc     <= 16'd0;
            c_xfer  <= c_xfer + 16'd1;
          end else begin
            acc <= acc_next;
          end
        end
        // [F3] A completion is never overwritten. fw_ack releases the
        // slot; a packet offered while it is occupied is dropped and
        // recorded rather than silently replacing data firmware has not
        // read yet.
        if (fw_ack)  done_r <= 1'b0;
        if (dropped) c_lost <= c_lost + 16'd1;
      end
    end
  end

  assign halted     = halt_r;
  assign xfer_done  = done_r;
  assign xfer_bytes = bytes_r;
  assign short_pkt  = short_r;
  assign n_xfer     = c_xfer;
  assign n_halt     = c_halt;
  assign n_lost     = c_lost;

endmodule

12. SystemVerilog — What A Type Removes, And What It Does Not

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  usb_ep_xfer_sv -- AFTER REVIEW, in SystemVerilog.
//
//  Identical hardware contract to usb_ep_xfer.v: same ports, same
//  completion rule, same reset scopes, same same-cycle priorities, same
//  one-clock latency.
//
//  The reason this version exists in a REVIEW chapter is that one of
//  the six findings stops being possible here. Finding F1 -- a USB bus
//  reset that also clears the endpoint configuration -- is a reset list
//  with one line too many. Gather exactly the fields a bus reset clears
//  into a type, and there is no list to get wrong:
//
//      d <= '0;
//
//  maxpkt and limit are not members, so they cannot be cleared here by
//  accident. A reviewer reading this file does not have to check a list
//  against another list; they have to check one type declaration.
//
//  This is worth stating precisely, because the opposite claim is a
//  common and damaging one: a stronger type removes a CLASS OF MISTAKE
//  from the code. It does not verify the design. F2 through F6 are all
//  still perfectly expressible here.
// =====================================================================
module usb_ep_xfer_sv (
  input  logic        clk,
  input  logic        rst_n,
  input  logic        usb_reset,

  input  logic        cfg_we,
  input  logic [6:0]  cfg_maxpkt,
  input  logic [15:0] cfg_limit,

  input  logic        pkt_valid,
  input  logic [6:0]  pkt_len,

  input  logic        halt_set,
  input  logic        halt_clr,

  input  logic        fw_ack,
  output logic        halted,
  output logic        ep_busy,
  output logic        xfer_done,
  output logic [15:0] xfer_bytes,
  output logic        short_pkt,

  output logic [15:0] n_xfer,
  output logic [15:0] n_halt,
  output logic [15:0] n_lost
);

  // Exactly the state a USB bus reset returns to default, and nothing
  // else. Every field resets to zero, which is why the whole reset is
  // one assignment.
  typedef struct packed {
    logic [15:0] acc;
    logic [15:0] bytes;
    logic        halt;
    logic        done;
    logic        short_f;
  } ep_data_t;

  localparam int EP_DATA_W = 16 + 16 + 3;

  ep_data_t    d;
  logic [6:0]  maxpkt;       // NOT in ep_data_t: firmware owns it
  logic [15:0] limit;        // NOT in ep_data_t
  logic [15:0] c_xfer, c_halt, c_lost;

  logic        busy_i, offered, accept, dropped, is_short, hit_limit, complete;
  logic [15:0] acc_next;

  // [F6] ONE name for "a completion is pending", read by the output
  // port, the accept gate and the drop detector alike.
  assign busy_i    = d.done;
  assign ep_busy   = busy_i;
  assign offered   = pkt_valid && !d.halt;
  assign accept    = offered && !busy_i;           // [F2] no length guard
  assign dropped   = offered &&  busy_i;           // [F3]
  assign acc_next  = d.acc + {9'd0, pkt_len};
  assign is_short  = (pkt_len < maxpkt);
  assign hit_limit = (acc_next >= limit);
  assign complete  = accept && (is_short || hit_limit);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      d      <= '0;
      maxpkt <= 7'd64;
      limit  <= '0;
      c_xfer <= '0; c_halt <= '0; c_lost <= '0;
    end else if (usb_reset) begin
      d <= '0;                                     // [F1] nothing to get wrong
    end else begin
      if (cfg_we) begin
        maxpkt <= cfg_maxpkt;
        limit  <= cfg_limit;
      end

      // [F5] one expression, and the clear wins because CLEAR_FEATURE is
      // the host's explicit recovery action.
      if (halt_clr) begin
        // [F4] the pending completion is part of the abandoned transfer
        d <= '0;
      end else if (halt_set) begin
        d.halt <= 1'b1;
        c_halt <= c_halt + 16'd1;
      end else begin
        if (accept) begin
          if (complete) begin
            d.bytes   <= acc_next;
            d.short_f <= is_short;
            d.done    <= 1'b1;
            d.acc     <= '0;
            c_xfer    <= c_xfer + 16'd1;
          end else begin
            d.acc <= acc_next;
          end
        end
        if (fw_ack)  d.done <= 1'b0;
        if (dropped) c_lost <= c_lost + 16'd1;
      end
    end
  end

  assign halted     = d.halt;
  assign xfer_done  = d.done;
  assign xfer_bytes = d.bytes;
  assign short_pkt  = d.short_f;
  assign n_xfer     = c_xfer;
  assign n_halt     = c_halt;
  assign n_lost     = c_lost;

`ifdef SVA_ON
  // ---------------------------------------------------------------
  //  Nine properties, each written against ONE review question from
  //  chapter 30.1. Icarus Verilog 13.0 rejects concurrent assertions,
  //  so under Icarus each is enforced by the named procedural check.
  // ---------------------------------------------------------------

  // [F3] SAFETY -- "can unread data be overwritten?"
  property p_no_overwrite;
    @(posedge clk) disable iff (!rst_n) accept |-> !d.done;
  endproperty
  a_no_overwrite: assert property (p_no_overwrite);

  // [F3] SAFETY -- a packet offered into a full slot is recorded, not
  // silently absorbed. The counter is the evidence the FIELD will have.
  property p_drop_is_recorded;
    @(posedge clk) disable iff (!rst_n)
      dropped |=> (c_lost == $past(c_lost) + 16'd1);
  endproperty
  a_drop_is_recorded: assert property (p_drop_is_recorded);

  // [F6] CONSISTENCY -- "is this derived, or is it a second copy?"
  property p_busy_is_done;
    @(posedge clk) disable iff (!rst_n) ep_busy == xfer_done;
  endproperty
  a_busy_is_done: assert property (p_busy_is_done);

  // [F2] BOUNDARY -- a zero-length packet is a packet, and it always
  // terminates the transfer because zero is shorter than every legal
  // maximum. This is the assertion that fails on the submitted design.
  property p_zlp_completes;
    @(posedge clk) disable iff (!rst_n)
      (accept && pkt_len == 7'd0) |=> d.done;
  endproperty
  a_zlp_completes: assert property (p_zlp_completes);

  // [F1] RESET SCOPE -- two obligations, and the second is the one that
  // gets left out: what a bus reset must NOT touch.
  property p_bus_reset_scope;
    @(posedge clk) disable iff (!rst_n)
      usb_reset |=> (d == {EP_DATA_W{1'b0}}) &&
                    (maxpkt == $past(maxpkt)) && (limit == $past(limit));
  endproperty
  a_bus_reset_scope: assert property (p_bus_reset_scope);

  // [F5] COLLISION -- the priority is a property, not a coding order.
  property p_clear_beats_set;
    @(posedge clk) disable iff (!rst_n)
      (halt_set && halt_clr) |=> !d.halt;
  endproperty
  a_clear_beats_set: assert property (p_clear_beats_set);

  // [F4] RESIDUE -- clearing a halt abandons the whole transfer.
  property p_unhalt_abandons_all;
    @(posedge clk) disable iff (!rst_n)
      halt_clr |=> (d.acc == '0) && (d.bytes == '0) && !d.done;
  endproperty
  a_unhalt_abandons_all: assert property (p_unhalt_abandons_all);

  // STABILITY -- a completion firmware has not read does not change
  // underneath it.
  property p_completion_stable;
    @(posedge clk) disable iff (!rst_n)
      (d.done && !fw_ack && !halt_clr && !usb_reset) |=> $stable(d.bytes);
  endproperty
  a_completion_stable: assert property (p_completion_stable);

  // PROGRESS -- the slot is released by the acknowledgement, in one
  // cycle, so a firmware that acknowledges cannot be blocked by this
  // module. Note the antecedent: it does not claim firmware WILL
  // acknowledge, which is not this module's obligation.
  property p_ack_releases;
    @(posedge clk) disable iff (!rst_n)
      (d.done && fw_ack && !halt_clr && !usb_reset) |=> !d.done;
  endproperty
  a_ack_releases: assert property (p_ack_releases);

  // COVER -- so that none of the above can pass by never happening.
  c_zlp:        cover property (@(posedge clk) accept && pkt_len == 7'd0);
  c_maxpkt:     cover property (@(posedge clk) accept && pkt_len == maxpkt);
  c_limit_end:  cover property (@(posedge clk) complete && !is_short);
  c_drop:       cover property (@(posedge clk) dropped);
  c_collision:  cover property (@(posedge clk) halt_set && halt_clr);
  c_zero_xfer:  cover property (@(posedge clk) complete && acc_next == '0);
  c_busreset:   cover property (@(posedge clk) usb_reset && (maxpkt != 7'd64));
`endif

endmodule

13. VHDL-2008 — What A Range Removes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  usb_ep_xfer (VHDL-2008) -- AFTER REVIEW.
--
--  Identical hardware contract to the Verilog-2005 and SystemVerilog
--  versions: same ports, same completion rule, same reset scopes, same
--  same-cycle priorities, same one-clock latency.
--
--  Two things VHDL contributes to a REVIEW that the other two do not.
--
--  The data state is a RECORD with a single default constant, so the
--  bus-reset scope is one assignment and finding F1 has no line to hide
--  in -- the same argument as the SystemVerilog struct, reached by a
--  different construct.
--
--  The byte accumulator is a RANGE-CONSTRAINED value. The contract in
--  the Verilog header records an assumption -- that cfg_limit plus
--  cfg_maxpkt minus one fits in sixteen bits -- and records that nothing
--  checks it. Here the range is declared, so the assumption is checked
--  by the simulator at the instant it is violated rather than wrapping
--  quietly. That is the seventh finding of 30.1 answered by a type.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity usb_ep_xfer is
  port (
    clk        : in  std_logic;
    rst_n      : in  std_logic;
    usb_reset  : in  std_logic;

    cfg_we     : in  std_logic;
    cfg_maxpkt : in  unsigned(6 downto 0);
    cfg_limit  : in  unsigned(15 downto 0);

    pkt_valid  : in  std_logic;
    pkt_len    : in  unsigned(6 downto 0);

    halt_set   : in  std_logic;
    halt_clr   : in  std_logic;

    fw_ack     : in  std_logic;
    halted     : out std_logic;
    ep_busy    : out std_logic;
    xfer_done  : out std_logic;
    xfer_bytes : out unsigned(15 downto 0);
    short_pkt  : out std_logic;

    n_xfer     : out unsigned(15 downto 0);
    n_halt     : out unsigned(15 downto 0);
    n_lost     : out unsigned(15 downto 0)
  );
end entity usb_ep_xfer;

architecture rtl of usb_ep_xfer is

  -- Exactly the state a USB bus reset returns to default, and nothing
  -- else. maxpkt and limit are declared separately, below, because
  -- firmware owns them and the host's reset does not revoke them.
  type ep_data_t is record
    acc     : unsigned(15 downto 0);
    bytes   : unsigned(15 downto 0);
    halt    : std_logic;
    done    : std_logic;
    short_f : std_logic;
  end record ep_data_t;

  constant EP_DATA_RESET : ep_data_t := (
    acc     => (others => '0'),
    bytes   => (others => '0'),
    halt    => '0',
    done    => '0',
    short_f => '0'
  );

  signal d      : ep_data_t := EP_DATA_RESET;
  signal maxpkt : unsigned(6 downto 0)  := to_unsigned(64, 7);
  signal limit  : unsigned(15 downto 0) := (others => '0');

  signal c_xfer, c_halt, c_lost : unsigned(15 downto 0) := (others => '0');

  signal busy_i, offered, accept_s, dropped : std_logic;
  signal is_short, hit_limit, complete  : std_logic;
  -- Seventeen bits so the SUM is representable even when the assumption
  -- in the header is violated; the assertion below is what reports it.
  signal acc_next : unsigned(16 downto 0);

begin

  -- [F6] ONE name for "a completion is pending", read by the output
  -- port, the accept gate and the drop detector alike.
  busy_i   <= d.done;
  ep_busy  <= busy_i;

  offered  <= pkt_valid and (not d.halt);
  accept_s <= offered and (not busy_i);     -- [F2] no length guard
  dropped  <= offered and busy_i;           -- [F3]

  acc_next <= ('0' & d.acc) + resize(pkt_len, 17);

  is_short  <= '1' when pkt_len < maxpkt else '0';
  hit_limit <= '1' when acc_next >= resize(limit, 17) else '0';
  complete  <= accept_s and (is_short or hit_limit);

  seq : process (clk, rst_n)
  begin
    if rst_n = '0' then
      d      <= EP_DATA_RESET;
      maxpkt <= to_unsigned(64, 7);
      limit  <= (others => '0');
      c_xfer <= (others => '0');
      c_halt <= (others => '0');
      c_lost <= (others => '0');
    elsif rising_edge(clk) then
      if usb_reset = '1' then
        d <= EP_DATA_RESET;                 -- [F1] nothing to get wrong
      else
        if cfg_we = '1' then
          maxpkt <= cfg_maxpkt;
          limit  <= cfg_limit;
        end if;

        -- [F5] one decision, one place, and the clear wins because
        -- CLEAR_FEATURE is the host's explicit recovery action.
        if halt_clr = '1' then
          d <= EP_DATA_RESET;               -- [F4] the whole transfer goes
        elsif halt_set = '1' then
          d.halt <= '1';
          c_halt <= c_halt + 1;
        else
          if accept_s = '1' then
            if complete = '1' then
              -- The assumption from the contract, checked rather than
              -- assumed. A limit set too close to the top of the range
              -- stops the simulation here instead of wrapping.
              assert acc_next <= 65535
                report "usb_ep_xfer: cfg_limit + cfg_maxpkt - 1 exceeds 16 bits"
                severity failure;
              d.bytes   <= acc_next(15 downto 0);
              d.short_f <= is_short;
              d.done    <= '1';
              d.acc     <= (others => '0');
              c_xfer    <= c_xfer + 1;
            else
              d.acc <= acc_next(15 downto 0);
            end if;
          end if;
          if fw_ack = '1' then
            d.done <= '0';
          end if;
          if dropped = '1' then
            c_lost <= c_lost + 1;
          end if;
        end if;
      end if;
    end if;
  end process seq;

  halted     <= d.halt;
  xfer_done  <= d.done;
  xfer_bytes <= d.bytes;
  short_pkt  <= d.short_f;
  n_xfer     <= c_xfer;
  n_halt     <= c_halt;
  n_lost     <= c_lost;

end architecture rtl;

The record does the same job as the struct: one constant, one assignment, no list. What VHDL adds on top is the seventh item from section 10 — the undocumented constraint — turned into something the simulator enforces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
              assert acc_next <= 65535
                report "usb_ep_xfer: cfg_limit + cfg_maxpkt - 1 exceeds 16 bits"
                severity failure;

A constraint that is checked is no longer an assumption. That is the strongest available answer to a LOW finding of that shape, and it costs one line.

14. Proving The Findings Are Real

A review finding is an assertion about the design, and an assertion nobody can falsify is an opinion. Every one of the six was therefore turned into a directed scenario, and the scenarios were run against the submitted design as a negative control:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    iverilog -g2005 -DUSE_BEFORE -o bad \
             usb_ep_xfer_before.v usb_ep_xfer.v tb_usb_ep_xfer.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    ** F1 config survives the bus reset: got 1 expected 0
    ** F2 the ZLP completes it:          got 0 expected 1
    ** F2 byte count:                    got 0 expected 32
    ** F2 reported as short:             got 0 expected 1
    ** F3 first is still there:          got 6 expected 4
    ** F3 the offer was recorded:        got 0 expected 1
    ** F3 slot released:                 got 1 expected 0
    ** F4 completion abandoned too:      got 1 expected 0
    ** F4 byte count cleared:            got 5 expected 0
    ** F5 the clear wins:                got 1 expected 0
    ** F6 zero-byte transfer completed:  got 0 expected 1
    ** F6 and still looks busy:          got 0 expected 1

    phase 1 findings   : 241 checks,    39 errors
    DIRECTED-ONLY      : 2,433 checks, 160 errors
    TOTAL              : 34,449 checks, 19,199 errors

Every finding fires, by name, in the scenario written for it. That is the difference between "I think this is wrong" and "here is the line of output".

15. Mutation As Review Evidence

The six findings, re-injected one at a time into the reviewed design, in all three languages:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    MUT     REVIEW ITEM                       V-DIR   SV-DIR   VH-DIR    V-ALL
    A-M1    Q3  reset scope                       6        6        6    8,229
    A-M2    Q6  boundary: the ZLP                86       86       86    9,324
    A-M3    Q7  liveness: overwrite              29       29       29    5,398
    A-M4    Q2  residue on abandonment            6        6        6   10,120
    A-M5    Q5  collision: which wins              3        3        3   14,171
    A-M6    Q2  authoritative state             106      106      106   11,046

BASE reads zero in all nine columns, and every directed column is identical across the three languages — twelve implementations of one contract, and the directed score depends only on the mutation.

Two numbers in that table are the interesting ones, and both are small.

A-M1 and A-M4 also score six. The same experiment applies and the same conclusion follows; they are not repeated here because the lesson is the lesson, not the number.

16. The Checklist

Now, and only now, the condensed form. It is useless without sections 4 to 9 — that is why it is at the end — but it is what you carry into the meeting.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  ARCHITECTURE AND CONTRACT
       [ ] is there a written contract, or only code?
       [ ] does it say what the block ASSUMES about its neighbours?
       [ ] does it say what it deliberately does NOT do?

    2  AUTHORITATIVE STATE
       [ ] one register per architectural fact
       [ ] every output is a function of registers, not of other outputs
       [ ] no signal that "should always agree" with another
       [ ] name the single source of truth so everything reads the same one

    3  RESET SCOPE
       [ ] list every register; put each in: cleared by both / by one / by
           neither, and give a reason for each
       [ ] the bus-reset list is checked in BOTH directions -- too long
           loses configuration, too short leaves stale state
       [ ] who wrote each surviving register, and do they know?

    4  DATA MOVEMENT
       [ ] every value has one path from input to output
       [ ] widths are explicit at every capture
       [ ] every conversion between a number and a bit vector is written

    5  PRIORITY AND COLLISIONS
       [ ] enumerate the input pairs that can arrive together
       [ ] for each: what wins, why, and WHERE is it decided
       [ ] the decision appears exactly once in the source
       [ ] a set and a clear of the same bit COMPOSE rather than race

    6  BOUNDARIES
       [ ] zero, one, max-1, max, max+1 for every counted quantity
       [ ] both sides of every comparison
       [ ] empty, full, one-before-full, one-after-release
       [ ] a zero-length or zero-count case is a CASE, not an absence

    7  LIVENESS
       [ ] every occupiable resource is eventually released
       [ ] every blocked producer is TOLD, not ignored
       [ ] fixed priorities have a bound, or a reason they need none
       [ ] a progress property exists, with a DERIVED bound

    8  OBSERVABILITY
       [ ] every "must not happen" in the contract has a counter
       [ ] the counter survives the reset that does not clear the cause
       [ ] somebody's firmware reads it

    9  EVIDENCE
       [ ] the directed suite passes without random stimulus
       [ ] the suite has been run against a design known to be wrong
       [ ] each finding has a scenario, and each scenario has been shown
           to be the one that catches it

17. Exercises

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    1  SEVERITY
       Re-rank F1 to F6 for a device that is bus-powered, has no firmware
       update path, and ships in a medical instrument. Which changes, and
       which does not? Justify each move by CONSEQUENCE, not by effort.

    2  THE MASKED FINDING
       F6 is unreachable while F2 is present. Find another pair in the
       submitted module where fixing one changes the reachability of
       another, and say which order a team should fix them in.

    3  THE SEVENTH ITEM
       Write the one sentence that belongs in the module header for the
       undocumented configuration constraint. Then write the assertion.
       Then say which of the two you would insist on in review, and why.

    4  VERILOG
       Add a second completion slot so that one transfer can complete while
       firmware still holds the previous one. State first what ep_busy now
       means, then what n_lost now means, then write it.

    5  SYSTEMVERILOG
       Implement the same two-slot contract. Does the struct still make F1
       impossible? Does it make anything else impossible that it did not
       before?

    6  VHDL
       Implement it, and declare the range of every new counter. Then
       deliberately configure a limit that violates the header's assumption
       and confirm the simulation stops at the violation.

    7  COLLISION AUDIT
       The two-slot version has input pairs the one-slot version does not.
       Enumerate them, decide each one, and say which are new.

    8  REVIEW WRITE-UP
       Write F3 as it would appear in a review tool: question, invariant,
       evidence, failure signature, false confidence, severity, and the
       specific change requested. One screen, no more.

    9  MUTATION PREDICTION
       For each of A-M1 to A-M6, predict whether it is caught by the
       findings phase, the exhaustive sweep, the stream phase, or only by
       random. Then check three. A-M2 and A-M6 are the surprising ones.

18. The Interview Answer

"You are handed 300 lines of RTL for a block you did not write and given an hour. What do you do?"

Not read it top to bottom. Reading linearly finds typos and misses everything that matters, because the defects that matter are relationships between lines that are far apart.

I start with the port list and the contract, and if there is no written contract that is the first finding — not pedantry, because without one there is nothing to review against and every subsequent discussion becomes a matter of taste. From the ports I draw the block: what state exists, who writes it, who reads it. Ten minutes, on paper, before opening the body.

That drawing produces the questions, and then I go looking for answers in a fixed order, because the order has dependencies. Authoritative state first: one register per fact, and every output a function of registers rather than of other outputs. Two signals that "always agree" is a finding every time, and the follow-up question — when exactly do they disagree — is usually answerable in one line.

Then reset scope, as a list in both directions. What must clear, and what must survive. On USB that means being explicit that a bus reset is not a chip reset: it clears the endpoint's data state and must not clear what firmware configured. I have seen the too-long version ship, and the symptom is a device that loses its configuration on every host suspend, reported as a host bug.

Then collisions: every pair of inputs that can arrive in the same cycle, and for each one, what wins and where that is decided. This is the highest-yield thing in the hour, because an if/else always produces an answer and nothing in the code distinguishes a decision from a default.

Then boundaries — zero and maximum on every counted thing, both sides of every comparison — and then the one people skip: liveness. Can work that is ready be starved? A design can be correct in every other respect and never do anything, and no data comparison will find it, because there is no wrong value to compare.

The last thing, and the one that separates a review from a reading: for every finding I raise, I say what evidence would settle it, and if I can, I produce that evidence. A scenario that fails on the submitted design and passes on the fixed one turns an opinion into a fact, and it takes fifteen minutes. It also tests the testbench, which is the subject of the next chapter — because a suite that passes on a design you know is broken has told you something important about the suite.

19. What Carries Forward

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE PROCEDURE
    o  nine questions, in an order that has dependencies -- architecture,
       state, reset, data, collisions, boundaries, liveness, observability,
       evidence
    o  every item has six parts, and FALSE CONFIDENCE is the one that does
       the work
    o  severity is a claim about consequence, not about effort

    THE FINDINGS THAT GENERALISE
    o  two signals that "should always agree" is a finding; the useful
       question is exactly when they disagree
    o  a reset list is wrong in two directions and both are shipping bugs
    o  an if/else always produces an answer, which is how it hides the
       question of whether anybody chose it
    o  a zero-length case is a CASE, not an absence
    o  a starved path corrupts nothing, so nothing that compares data can
       find it
    o  every "must not happen" in a contract deserves a counter, because an
       assertion is not present in silicon
    o  a constraint that is documented is an assumption; a constraint that
       is checked is not

    THE METHOD
    o  FIXING ONE FINDING CAN REVEAL ANOTHER -- F6 is unreachable until F2
       is fixed -- so review the whole block before changing any of it, and
       record the interaction
    o  a stronger type removes a CLASS of mistake and no more: the struct
       makes F1 unwritable and leaves five findings untouched
    o  run the suite against a design known to be wrong; a checker never
       shown to fail has not been validated
    o  an exhaustive sweep is exhaustive over the axes it HAS, and
       "two things in one cycle" is rarely one of them
    o  a scenario that is the sole detector of a finding is load-bearing,
       measured by deleting it: A-M5 goes from 3 to 0

The next chapter turns the review on the other artefact. Every number in this one came from a testbench, and a testbench is engineered software that can be wrong in ways a passing run will never reveal — including, as it turns out, two ways that this chapter's own benches were.

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.