Skip to content

PCIe · Module 30

Verification Checklist — Can This Environment Fail?

A predictor fed from the DUT's own output agrees with itself forever, and ten thousand passing tests prove nothing. The gate is whether every contract has independent stimulus, an independent checker, and a demonstrated failure signature.

30.2 asked whether the RTL is faithful. This gate asks something uncomfortable about the thing that judged it: is this environment capable of failing?

1. What This Gate Owns

Verification is complete only when every architectural contract has independent stimulus, an independent checker, closure evidence and a known failure signature.

Four requirements, and dropping any one of them produces a familiar kind of false confidence.

MissingWhat you get
independent stimulusa contract nothing exercises — passing by never being tried
an independent checker§4's circular predictor: agreement with yourself
closure evidence"we ran a lot of tests"
a known failure signaturea checker nobody has ever seen fail — §7

Three readings.

Test count is not closure. Ten thousand tests over one path is one test run ten thousand times. The measure is contracts covered, not seeds consumed.

Coverage percentage is not closure either. Code coverage says which lines executed; it says nothing about whether the interesting simultaneous conditions (30.2 §1) ever co-occurred. §9 is about what to measure instead.

And the fourth requirement is the one this chapter is really about. A checker that has never fired is not a checker; it is a hypothesis. §7 and §8 exist to convert hypotheses into evidence.

This gate inherits every contract recorded by 30.1 §4 and every hook required by 30.2 §15 Q48. It does not re-teach UVM — 24.1, 24.2, 24.3, 24.4, 24.5, 24.6 and 24.7 own the mechanisms.

2. Independence, Drawn

A UVM verification environment block diagram. A sequence drives a driver which stimulates the DUT. An input-side monitor observes the stimulus and feeds a reference model or predictor, producing an expected stream. An output-side monitor observes the DUT's responses, producing an actual stream. Both streams meet at a scoreboard. Coverage and a fault injection agent are attached alongside, and a configuration and reset agent drives the DUT independently.SequenceintentDriverdrives the interfaceInput monitorobserves — neverdrivesDUTthe endpoint RTLReference modelEXPECTED — from inputonlyOutput monitorACTUALScoreboardtwo independentsources12
A UVM environment drawn to make one property obvious: the expected path and the actual path must share no source. Stimulus drives the DUT through a driver; the input-side monitor feeds the reference model, which produces the expected result; the output-side monitor produces the actual result. The scoreboard compares two things that were derived independently. Section 4 shows what happens when the two paths quietly share an origin.

The property to read off the picture: there is no path from the DUT's output to the expected stream. Trace backwards from the scoreboard's two inputs and they meet only at the stimulus. If they meet anywhere else, §4 applies.

3. Requirement Traceability

One row per contract from 30.1 §4. A requirement is not verified until every column is filled.

RequirementStimulusCheckerCoverageNegative testReset caseOwnerStatus
tag never reused while liverandom alloc/retire at high occupancya_no_double_alloc + scoreboardtag-reuse-after-retire binforce a double alloc → must failreuse across RecoveryDVCLOSED
Completions correlated by tagout-of-order Completion driverscoreboard by tagout-of-order bindeliver out of order → must failCompletion after resetDVCLOSED
split Completions accumulate statusmulti-part Completions, mixed statusscoreboard status_accsplit-count × status binsgood after bad → must failsplit across RecoveryDVCLOSED
config atomic w.r.t. trafficcommit under loada_cfg_stable_when_busycommit-while-busy bincommit with work liveDVCLOSED
outstanding limit respectedsaturating loada_count_matches_tablehigh-water at limitforce over-allocationDVCLOSED
descriptor ownership handoffOPEN — no stimulus

Three readings.

A requirement with stimulus and no checker is not verified — the design was exercised and nobody looked. A requirement with a checker and no negative test is weakly verified: the checker exists and may be inert.

The last row is what the matrix is for. "Descriptor ownership handoff" has a contract from 30.1 §13 Q17 and nothing behind it. In a test-count-based review that gap is invisible; in this matrix it is one blank row.

And "Status: CLOSED" should mean the negative test ran and failed for the right reason — not that the checker exists. §7 makes that a gate.

4. The Circular Predictor

The most damaging verification failure available, because its symptom is universal success.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG. ILLUSTRATIVE. The expected stream is built from the DUT's own output.
// This happens by increments: someone needs a field the input side does not
// carry, takes it from the response "just for now", and the environment is
// hollow from then on.
class pcie_env extends uvm_env;
  function void connect_phase(uvm_phase phase);
    // The output monitor feeds BOTH the predictor and the scoreboard's actual
    // port. The two are now the same observation with extra steps.
    out_mon.ap.connect(predictor.analysis_export);   // <-- expected
    out_mon.ap.connect(sb.actual_export);            // <-- actual
    predictor.ap.connect(sb.expected_export);
  endfunction
endclass

Why it is so hard to spot. Nothing looks wrong at the call site. Each line is a legitimate TLM connection; the class names are right; the predictor genuinely computes something. The defect is in the graph, not in any statement — and code review reads statements.

What it produces.

SymptomReality
every test passesthe scoreboard compares the DUT to itself
regression is green for monthsit was green on day one, for the same reason
coverage rises normallystimulus is real; only the judgement is hollow
a real bug is introducedstill passes — the expected value moves with the actual

The detection method is structural, and it takes minutes. Trace both scoreboard inputs backwards and find where they meet. They must meet at the stimulus and nowhere else. Any shared ancestor downstream of the DUT is a fail — and this should be an explicit review artefact, not a hope.

The corrected connection.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECT. Expected descends from the INPUT side only. If the input side does
// not carry enough information to predict the output, that is a finding about
// the reference model — not a licence to peek at the answer.
in_mon.ap.connect(predictor.analysis_export);      // expected: input-derived
predictor.ap.connect(sb.expected_export);
out_mon.ap.connect(sb.actual_export);              // actual: output-derived

And the confirming test is §8's mutation. Break one RTL condition. If the regression stays green, the environment is circular somewhere — this is the one check that detects the failure without reading any code.

5. Monitor Sampling

A monitor publishes events, and 30.2 §2's question applies unchanged: which event?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. A passive monitor. Every decision here is a review item.
class pcie_req_monitor extends uvm_monitor;
  `uvm_component_utils(pcie_req_monitor)
  virtual pcie_if      vif;
  uvm_analysis_port #(pcie_req_txn) ap;
 
  task run_phase(uvm_phase phase);
    pcie_req_txn tx;
    forever begin
      @(vif.mon_cb);
 
      // WRONG: if (vif.mon_cb.req_valid)
      //   -- a held offer under back-pressure publishes the SAME request once
      //      per cycle, and the scoreboard sees N requests where there was one.
      //      This is 30.2 §3's bug, reproduced in the testbench, where it is
      //      harder to see because nothing in the DUT is wrong.
      if (vif.mon_cb.req_valid && vif.mon_cb.req_ready) begin
        tx = pcie_req_txn::type_id::create("tx");
        tx.tag        = vif.mon_cb.req_tag;
        tx.bytes      = vif.mon_cb.req_bytes;
        tx.timestamp  = $time;
 
        // Publish a COPY. Analysis subscribers may queue the handle for many
        // cycles; reusing one object overwrites data the scoreboard has not
        // read yet, producing "wrong" comparisons with no DUT bug behind them.
        ap.write(tx);
      end
    end
  endtask
endclass

Six lenses. Architecture: a passive observer of the same events the RTL acts on, so DUT and model agree on what happened. State: none intentionally — a monitor that accumulates state is a second model, and it will drift. Event: req_valid && req_ready, sampled through a clocking block so there is no race with the driver. Contract: subscribers may hold the handle indefinitely, so each publish must be a fresh object. Failure: sampling valid alone duplicates transactions under back-pressure; reusing one object corrupts queued data and produces phantom mismatches that consume days. DV/debug: the mutation in §8 — change the sample condition and confirm the environment notices.

And the clocking block is not a formality. Sampling combinationally races the driver's non-blocking updates, and the resulting failures are seed-dependent and simulator-dependent — the worst debugging category, because reproduction is unreliable.

6. Scoreboard Identity

Comparing by order works until the design is allowed to reorder — and PCIe permits Completions to be split and delivered out of order (13.3 · 13.4).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Identity-based scoreboard. Keyed by tag AND generation, so a
// reused tag is a different entry rather than the same one twice.
class pcie_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(pcie_scoreboard)
 
  typedef struct {
    int unsigned bytes_expected;
    int unsigned bytes_seen;
    bit [1:0]    status_acc;
    time         issued;
  } entry_t;
 
  // Associative array keyed by {generation, tag} — NOT a queue. A queue encodes
  // an ordering assumption that the protocol does not owe us.
  entry_t m_live [bit [11:0]];
 
  int unsigned m_orphan, m_dup, m_stale;
 
  function bit [11:0] key(bit [3:0] gen, bit [7:0] tag);
    return {gen, tag};
  endfunction
 
  // Expected: a request was accepted.
  function void write_expected(pcie_req_txn tx);
    bit [11:0] k = key(tx.gen, tx.tag);
    if (m_live.exists(k))
      `uvm_error("SB", $sformatf("tag %0d gen %0d allocated while live", tx.tag, tx.gen))
    m_live[k] = '{bytes_expected: tx.bytes, bytes_seen: 0, status_acc: 0, issued: $time};
  endfunction
 
  // Actual: a Completion was observed.
  function void write_actual(pcie_cpl_txn tx);
    bit [11:0] k = key(tx.gen, tx.tag);
 
    if (!m_live.exists(k)) begin
      // Distinguish the two ways this happens — they are different bugs and
      // reporting them as one number loses the diagnosis (29.6 §7's lesson).
      if (m_seen_gen.exists(tx.tag) && (tx.gen != m_seen_gen[tx.tag])) begin
        m_stale++;   // a stale generation: 30.2 §7's tag-reuse hazard
        `uvm_error("SB", $sformatf("stale completion tag %0d gen %0d", tx.tag, tx.gen))
      end else begin
        m_orphan++;  // no request ever matched this
        `uvm_error("SB", $sformatf("orphan completion tag %0d", tx.tag))
      end
      return;
    end
 
    m_live[k].bytes_seen  += tx.bytes;
    m_live[k].status_acc  |= tx.status;      // accumulate — never overwrite
 
    if (m_live[k].bytes_seen > m_live[k].bytes_expected)
      `uvm_error("SB", $sformatf("over-delivery tag %0d", tx.tag))
 
    if (m_live[k].bytes_seen == m_live[k].bytes_expected) begin
      m_seen_gen[tx.tag] = tx.gen;
      m_live.delete(k);                       // retire exactly once
    end
  endfunction
 
  // The check most environments omit: nothing is still outstanding at the end.
  function void check_phase(uvm_phase phase);
    foreach (m_live[k])
      `uvm_error("SB", $sformatf("request never completed: key %0h, issued %0t",
                                 k, m_live[k].issued))
  endfunction
endclass

Six lenses. Architecture: an associative array keyed by identity, because the protocol does not owe us ordering. State: one entry per live request, with bytes and accumulated status. Event: write_expected on an accepted request; write_actual on an observed Completion; retirement only when the byte count is exactly met. Contract: the monitors must supply gen, which requires 30.2 §15 Q48's visibility — a scoreboard cannot check what the interface does not expose. Failure: using a queue silently asserts FIFO ordering (30.1 §9); deleting on the first Completion loses split data; |= replaced by = turns a failed transfer into a successful one. DV/debug: the three counters m_orphan, m_dup, m_stale distinguish three different bugs that a single "mismatch" count merges.

And check_phase is the quietly important part. A test that ends with entries still in m_live had requests that never completed, and without this loop the test passes. The most common real bug it catches is the environment's own — see §11.

7. Every Checker Must Have Been Seen to Fail

Rule: for each checker, a negative test exists, is run, and fails for the stated reason.

InjectionMust fail with
duplicate Completion for one requestover-delivery, or duplicate-retire
Completion with a stale generationm_stale increments and the test errors
a Completion that never arrivescheck_phase reports the outstanding entry
out-of-order Completionspasses — legal (13.4); the test proves the scoreboard tolerates it
illegal state transitionthe FSM assertion
credit accounting errorcredit checker (16.5 · 16.6); the deadlock case is 25.8
config change with work outstandinga_cfg_stable_when_busy
reset with requests livethe reset-scope checker

Three readings.

A checker that has never fired is a hypothesis. It may be mis-wired, mis-keyed, subscribed to the wrong port, or disabled by a filter — and none of those are visible while everything passes. The negative test converts it to evidence.

Row 4 is deliberately inverted and belongs on the list. Out-of-order delivery must pass. A "negative" suite where every case fails is testing the checkers' sensitivity and not their specificity — and an over-sensitive scoreboard that flags legal behaviour gets its errors demoted to warnings, which disables it.

And "fails for the stated reason" is stricter than "fails." A test that fails with a null-pointer error in the environment has not demonstrated the checker. The error message matters, which is why §6's scoreboard distinguishes stale from orphan.

8. Mutation Testing — the Gate That Cannot Be Faked

Break the RTL deliberately. If the environment stays green, verification is not closed.

MutationShould be caught by
valid && readyvalidduplicate-transaction check (30.2 §3)
remove the generation term from cpl_matchm_stale / stale-Completion test
retire on first Completion instead of finalbyte-count mismatch on splits
status_acc |= sstatus_acc = sgood-after-bad negative test
two NBAs on the outstanding countera_count_matches_table (30.2 §13)
remove the config quiescence gatea_cfg_stable_when_busy
widen a reset's scopereset-containment test

Three readings.

This is the only check that detects a circular predictor without reading code (§4). A hollow environment cannot detect any mutation, so a single surviving mutation is a strong signal and a row of surviving mutations is a diagnosis.

A surviving mutation has exactly three explanations, and all three are findings: the stimulus never reaches that condition; the checker cannot see it; or the checker is not independent. None is acceptable at sign-off.

And the mutations should be drawn from 30.2's wrong-RTL patterns, not invented. The list above is the previous chapter's failure catalogue used as a test suite — which is the cheapest way to build a mutation set that targets realistic bugs rather than arbitrary ones.

9. Semantic Coverage

Coverage percentage is not the goal. The question is: which important combinations were never observed?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Cross-coverage aimed at the conditions 30.2 says actually
// break endpoint RTL — simultaneity, occupancy extremes, and reset under load.
covergroup cg_pcie_semantic @(posedge clk);
  cp_class    : coverpoint req_class      { bins b[] = {BULK, STREAM, PROGRESS}; }
  cp_depth    : coverpoint outstanding_q  { bins low = {[0:7]};
                                            bins mid = {[8:47]};
                                            bins near_max = {[48:63]};
                                            bins at_max   = {64}; }
  cp_bp       : coverpoint req_ready      { bins stalled = {0}; bins open = {1}; }
  cp_reset    : coverpoint reset_kind     { bins none, recovery, func, local; }
  cp_err      : coverpoint err_kind       { bins none, cpl_status, timeout, ecrc; }
  cp_cfg      : coverpoint cfg_commit_fire{ bins idle = {0}; bins commit = {1}; }
 
  // The simultaneity bin. This is 30.2 §4's coincidence, and it will not arise
  // reliably from random stimulus at low occupancy.
  cp_simul    : coverpoint {alloc_fire, retire_fire} { bins both = {2'b11}; }
 
  // The crosses that matter. Each names a real failure from Module 29 or 30.2.
  x_depth_bp   : cross cp_depth, cp_bp;        // window binds under back-pressure
  x_reset_load : cross cp_reset, cp_depth;     // reset with work outstanding
  x_cfg_load   : cross cp_cfg,  cp_depth;      // commit while busy
  x_err_class  : cross cp_err,  cp_class;      // error on each class
endgroup

Six lenses. Architecture: coverage aimed at conditions, not lines. State: the sampled variables must be the same ones the checkers use, or coverage certifies a state the scoreboard never judged. Event: sampled every cycle here; a per-transaction group would miss cp_simul. Contract: every bin must be reachable — an unreachable bin is a permanently open item that eventually gets excluded, taking real gaps with it. Failure: at_max never hit means the outstanding limit was never reached, so 30.2 §15's bound is untested. DV/debug: x_reset_load with only the low depth bin filled means reset was only ever tested near-idle — §10's gap exactly.

And the review question is the inverse of the usual one. Not "what percentage?" but "list the crosses at zero, and justify each." Most will be genuinely unreachable or genuinely unimportant; the two or three that are neither are the finding.

10. Reset and Recovery Under Load

Testing reset from idle verifies that registers clear. It verifies nothing about lifetimes.

Required scenarioWhat it catches
requests outstanding → link Recovery (18.5) → traffic resumes30.2 §7's orphaned-work class
Completion arriving during Recoverythe window nobody models
Completion arriving after a reset that discarded its requeststale-match; must increment m_stale, never retire
function reset with a sibling function busy29.6 §14's containment, one level down
config commit interrupted by resethalf-committed configuration
reset during a split Completion sequencepartial bytes_left state

Two readings.

The scoreboard needs a reset contract of its own, and this is where environments quietly break: on reset, does the scoreboard discard live entries, retain them, or mark them abandoned? All three are implementable and only one matches the architecture (30.1 §6's matrix). An environment that discards will not notice the DUT retiring an orphan.

And these scenarios need a reset agent independent of the sequence, because the interesting timings are between transactions, not between tests. A reset that only ever happens at test boundaries tests nothing on this list.

11. End-of-Test Drain

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG. ILLUSTRATIVE. The objection is dropped when the driver has taken the
// last item. Sequence completion is treated as semantic completion.
task body();
  repeat (N) begin
    `uvm_do(req)                  // driver accepts...
  end
  // BUG: the last request's Completion may be hundreds of cycles away. The
  //      test ends, the scoreboard's check_phase runs against a half-finished
  //      state, or shutdown begins before the response arrives at all.
endtask

Failure — the timeline.

TimeEventScoreboard
Tlast request accepted by the driver1 entry live
T+1sequence body returns1 live
T+2objection dropped; shutdown begins1 live
T+3check_phase runsreports the outstanding entry — or does not run in time
T+400the Completion would have arrivednobody is listening

Two outcomes, and both are bad. Either the test fails spuriously on a request that would have completed — and the usual fix is to delete the check_phase loop, removing a genuine checker; or check_phase is not reached in time and the test passes without ever checking the last transactions.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECT. Drain on SEMANTIC completion — outstanding work is zero — with an
// explicit bound so a hang fails loudly instead of hanging the regression.
task body();
  repeat (N) `uvm_do(req)
  fork
    begin : drain
      wait (sb.outstanding() == 0);
    end
    begin : watchdog
      #(DRAIN_TIMEOUT_NS * 1ns);
      `uvm_error("DRAIN", $sformatf("%0d requests outstanding at drain timeout",
                                    sb.outstanding()))
    end
  join_any
  disable fork;
endtask

Six lenses. Architecture: the test ends when the design is finished, not when the sequence is. State: the scoreboard's live count is the authority — it already exists (§6). Event: outstanding() == 0, or the watchdog. Contract: the drain bound must come from the architecture's completion-timeout policy (30.1 §13 Q11), not a round number. Failure: an unbounded wait converts a design hang into a regression hang, which is worse — it consumes a machine and produces no diagnosis. DV/debug: the error message reports the count, so a drain failure names how much work was stranded.

12. Assertions Versus Scoreboard Versus Formal

They catch different things, and using one for another's job leaves a hole.

PropertyBest toolWhy
no double tag allocationformalsmall state, bounded — provable in minutes
occupancy never exceeds boundformalinductive
legal FSM transitionsformalexhaustive over states
config commit atomicformala safety property
payload stable while stalledSVAcycle-level, needs no model
Completion only for live tagSVAinternal state visible
data content correct end to endscoreboardrequires a model, not just a property
split reassembly correctscoreboardaccumulation across events
nothing outstanding at endscoreboarda whole-test property
throughput meets targetneither30.5's job, with a performance model

The interface assertions the environment itself must carry. These are distinct from 30.2 §13's — those watch internal state, these watch the boundary and belong in the interface so every environment that binds it inherits them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Bound to the interface, not the DUT. These are the properties a
// testbench is entitled to assume, and the reason to put them here rather than
// in the scoreboard is that they fail at the CYCLE of the violation instead of
// at the transaction that eventually goes wrong.
interface pcie_req_if (input logic clk, input logic rst_n);
  logic        req_valid, req_ready;
  logic [7:0]  req_tag;
  logic [15:0] req_bytes;
 
  // English: an offer, once made, is not withdrawn before acceptance. Catches a
  // driver that pulls valid low on a stall — which corrupts the DUT's view and
  // is a TESTBENCH bug that presents as a DUT bug.
  a_if_no_retract: assert property (
    @(posedge clk) disable iff (!rst_n)
      (req_valid && !req_ready) |=> req_valid
  );
 
  // English: the payload is stable while the offer is pending. The mirror of
  // 30.2 §3's property, asserted from the other side of the boundary.
  a_if_stable: assert property (
    @(posedge clk) disable iff (!rst_n)
      (req_valid && !req_ready) |=> ($stable(req_tag) && $stable(req_bytes))
  );
 
  // English: no request is offered with a zero byte count. A degenerate request
  // is a stimulus bug, and without this it reaches the scoreboard as a
  // never-completing entry reported at check_phase — hundreds of cycles from
  // its cause.
  a_if_nonzero: assert property (
    @(posedge clk) disable iff (!rst_n)
      req_valid |-> (req_bytes != '0)
  );
 
  // English: a tag is not offered again while the scoreboard still has it live.
  // A stimulus-side guard: the sequence must not generate a colliding tag, and
  // this fires at generation rather than at the resulting mismatch.
  a_if_tag_not_reoffered: assert property (
    @(posedge clk) disable iff (!rst_n)
      (req_valid && req_ready) |-> !sb_tag_live[req_tag]
  );
endinterface

Six lenses. Architecture: interface-bound, so every environment inherits them and no testbench can omit them by accident. State: none of their own except the scoreboard's live vector, referenced read-only. Event: the same valid && ready boundary §5's monitor samples — deliberately the same event, so a disagreement between assertion and monitor is impossible. Contract: these state what the testbench owes the DUT, which is the half usually left unwritten; §7's negative tests must not violate them accidentally. Failure: without a_if_no_retract, a driver that withdraws an offer produces DUT behaviour that looks like an RTL bug and consumes days. DV/debug: each fires at the cycle of the violation rather than at the transaction that eventually mismatches, which is typically hundreds of cycles earlier and is the entire value.

And a_if_nonzero earns its place for a reason worth generalising. A zero-byte request is a stimulus defect, and without the assertion it surfaces as a scoreboard entry that never completes — reported by check_phase at end of test, maximally distant from its cause. Cheap boundary assertions convert late, confusing failures into immediate, obvious ones.

Two readings.

Formal's sweet spot here is unusually large — the first four are all small-state safety properties on structures (30.2 §6's tag table) that fit comfortably. These are the properties random stimulus is worst at, because they require specific simultaneous conditions.

And the last row is a boundary worth defending. Formal does not prove system throughput, and an assertion cannot check reassembled data content. Claiming either is how a gap gets closed on paper.

13. The Verification Checklist — 47 Questions

Plan and traceability

#QuestionWhyEvidenceFAIL if
1Does every 30.1 §4 contract have a traceability row?§3the matrixcontracts without rows
2Does every row have stimulus and a checker?§3both columnsstimulus only
3Does every checker have a negative test that ran?§7the failing run"the checker exists"
4Is any requirement closed on test count alone?§1closure criteria"10 000 tests pass"
5Are open items listed as open rather than absent?§3's last rowthe OPEN rowsa matrix with no gaps at all

Environment independence

#QuestionWhyEvidenceFAIL if
6Where does expected data originate?§4the connectivity traceany DUT-output ancestry
7Has someone traced both scoreboard inputs to their common ancestor?§4the trace, as an artefactnever done
8Does the reference model share code with the RTL?shared bugs cancelseparate implementationsgenerated from the same source
9Is the predictor driven by the input monitor only?§4connect_phasemixed sources
10Do mutations survive?§8the mutation reportnot run

Monitors

#QuestionWhyEvidenceFAIL if
11Does the monitor sample valid && ready?§5the conditionvalid alone
12Is sampling through a clocking block?§5the blockcombinational sampling
13Is a fresh object published per event?§5create per publishone reused handle
14Is the monitor passive — no state, no driving?§5the codeaccumulates state
15Does the monitor see everything the checker needs?30.2 §15 Q48the interfaceinternal-only signals

Scoreboard

#QuestionWhyEvidenceFAIL if
16Is matching by identity, not order?§6associative arraya queue
17Does the key include the generation?§6 · 30.2 §7the key functiontag only
18Are split Completions accumulated by byte count?§6bytes_seenretire on first
19Does status accumulate rather than overwrite?§6|==
20Are orphan, duplicate and stale reported separately?§6three countersone "mismatch"
21Does check_phase report entries still live?§6the loopabsent
22Is over-delivery detected?§6the comparisononly exact-match checked
23Is there a scoreboard reset contract, and does it match architecture?§10the stated behaviourundefined

Stimulus and sequences

#QuestionWhyEvidenceFAIL if
24Is back-pressure exercised on every interface?§5 · 30.2 §3 · 29.3 §4cp_bp coverageready always high
25Is the outstanding limit actually reached?§9at_max binnever hit
26Are Completions delivered out of order?§7 row 4the ordering testin-order only
27Are split Completions generated with varied part counts?13.3 · 13.1the binssingle-part only
28Is there a reset agent independent of sequences?§10the agentreset at test boundaries only
29Is concurrent multi-class traffic generated?29.6 §17 · 21.4the cross binsone class at a time

Coverage

#QuestionWhyEvidenceFAIL if
30Is coverage semantic, not only code coverage?§9the covergroupsline coverage only
31Is the both-fire-same-cycle bin covered?30.2 §4cp_simulabsent
32Is reset crossed with occupancy?§10x_reset_loadreset only at idle
33Is config commit crossed with load?§9x_cfg_loadcommit only when idle
34Are zero-count crosses listed and each justified?§9the justification listreported as a percentage only
35Are exclusions reviewed rather than accumulated?exclusions hide gapsthe exclusion reviewa long unreviewed list

Assertions and formal

#QuestionWhyEvidenceFAIL if
36Are 30.2 §13's assertions present and enabled in regression?§12the run logcompiled out
37Has each assertion been observed to fire at least once?§7the negative testsnever fired
38Are formal targets identified and run?§12the formal report"we might later"
39Are formal results scoped honestly (proven vs bounded)?a bounded proof is not a proofthe depth"proven" unqualified
40Is anything claimed proven by formal that formal cannot prove?§12 last rowthe claim listthroughput claimed

Errors, reset, drain, debug

#QuestionWhyEvidenceFAIL if
41Is each error type injected and its handling checked?24.7 · 25.7per-type testserror path untested
42Is first-fault stickiness tested with a second error?last-error-winsthe two-error testone error only
43Is reset tested with work outstanding?§10x_reset_loadidle reset only
44Is a late Completion after a reset tested?§10m_stale incrementsuntested
45Does the test end on semantic completion, not item_done?§11the drain conditionobjection dropped early
46Is the drain bounded, with a diagnostic on timeout?§11the watchdogunbounded wait
47On failure, does the environment report enough to diagnose without a rerun?debug costthe message content"mismatch"

14. Misconceptions

"Ten thousand tests pass, so we're done." §1, §4: if the predictor is circular they would pass with no DUT at all.

"Coverage is at 98 %." §9: of lines. The question is which crosses are at zero, and whether each is justified.

"The checker is there, so the requirement is covered." §7: a checker that has never fired is a hypothesis — it may be mis-keyed, mis-connected or filtered out.

"Formal proves it." §12: formal proves small-state safety properties beautifully and does not prove throughput (22.1) or data content.

"Reset is tested." §10: from idle, which verifies that registers clear. The lifetime questions all live under load.

"item_done means the transaction finished." §11: it means the driver finished. The Completion may be hundreds of cycles away.

"Out-of-order delivery should fail the test." §7 row 4: it is legal. A suite where every negative case fails is testing sensitivity, not specificity — and an over-sensitive scoreboard gets demoted to warnings.

"The monitor is passive, so it can't be wrong." §5: sampling valid alone duplicates transactions; reusing one object corrupts queued data. Both produce failures with no DUT bug behind them.

15. Understanding Check

Q1. A regression has been green for eight months. What is the first thing to check?

Where the expected data comes from (§4). If the output monitor feeds both the predictor and the scoreboard's actual port, the scoreboard is comparing the DUT to itself and would have been green on day one for the same reason — including with a real bug injected, because the expected value moves with the actual. It is hard to spot because the defect is in the connection graph, not in any statement: every line of connect_phase is a legitimate TLM call, and code review reads statements.

Two checks, and the second needs no code reading. Structurally, trace both scoreboard inputs backwards and find their common ancestor — it must be the stimulus and nothing downstream of the DUT; this should be a review artefact, not a hope. Behaviourally, run mutations (§8): break valid && ready to valid, remove the generation term from the Completion match, change status_acc |= to =. A hollow environment cannot detect any of them, so a row of surviving mutations is a diagnosis rather than a hint.

Q2. Why is "the checker exists" not enough, and what is the standard instead?

Because a checker that has never fired is a hypothesis (§7). It may be subscribed to the wrong port, keyed on the wrong field, or disabled by a filter — and none of those are visible while everything passes. The standard is a negative test that ran and failed for the stated reason: inject a duplicate Completion, a stale generation, a missing Completion, a good-status-after-bad split.

Two refinements matter. "Fails for the stated reason" is stricter than "fails" — a null-pointer crash in the environment demonstrates nothing, which is why §6's scoreboard distinguishes m_stale from m_orphan from m_dup. And the negative suite must include a case that must PASS: out-of-order Completion delivery is legal (13.4), and a suite where everything fails is testing the checkers' sensitivity while ignoring their specificity. An over-sensitive scoreboard that flags legal behaviour gets its errors demoted to warnings, which disables it entirely.

Q3. A test drops its objection after the last uvm_do. What are the two outcomes?

Both bad, and one of them is silent (§11). The last request's Completion may be hundreds of cycles away. Either check_phase runs against a half-finished state and the test fails spuriously on a request that would have completed — and the usual "fix" is to delete the check_phase loop, removing a genuine checker — or shutdown outruns the check and the test passes without ever examining the final transactions.

The correct drain is on semantic completion: wait (sb.outstanding() == 0), using the scoreboard's live count, which §6 already maintains. It must be bounded, forked against a watchdog that reports the stranded count — because an unbounded wait converts a design hang into a regression hang, which consumes a machine and produces no diagnosis. And the bound should come from the architecture's completion-timeout policy (30.1 §13 Q11), not a round number.

Q4. Design the coverage that would catch the failure class 30.2 says actually breaks endpoint RTL.

Target simultaneity, occupancy extremes and reset under load — none of which are line coverage (§9). The specific bins: cp_simul = {alloc_fire, retire_fire} == 2'b11, the coincidence that 30.2 §4's two-NBA counter loses, which will not arise reliably from random stimulus at low occupancy; cp_depth.at_max, because an outstanding bound that is never reached is never tested; and cp_bp.stalled, because with ready always high the valid-versus-valid && ready bug cannot appear.

Then the crosses, each named after a real failure. x_reset_load — reset crossed with occupancy, because reset from idle only verifies that registers clear and every lifetime question lives under load (§10). x_cfg_load — commit crossed with outstanding work, which is a_cfg_stable_when_busy's stimulus. x_err_class — each error type on each traffic class.

And the review question is inverted from the usual one. Not "what percentage?" but "list every cross sitting at zero and justify each." Most will be genuinely unreachable or unimportant; the two or three that are neither are the finding. Exclusions must be reviewed rather than accumulated, because a long unreviewed exclusion list is where real gaps go to hide.

16. What Comes Next

This gate asked whether the environment can fail. The next asks whether any of it survives contact with the SoC.

30.4 covers the failures that appear only when the subsystem meets real clocks, resets, an address map, an IOMMU, firmware and software — disagreements between individually reasonable assumptions, where the PCIe traffic is perfectly legal and lands in the wrong memory.