UCIe · Module 27
Verification Review Checklist
The DV signoff gate — why coverage of contracts beats coverage of code, how to audit a predictor for independence rather than trust it, the monitor and scoreboard defects that make a testbench agree with the design's bugs, and why twenty thousand passing tests at full code coverage can still be a FAIL.
27.1 closed the decisions; 27.2 checked the code implements them. This gate asks the harder question: could this environment have told us if it did not?
1. The One-Sentence Model
Verification completeness is coverage of contracts, not coverage of code — and an environment is only checking something when the expected value comes from somewhere other than the thing being checked.
The failure this gate exists to catch is a green regression. Twenty thousand tests, full code coverage, zero failures — and a scoreboard that would have passed regardless of what the design did (§23). Nothing in a pass/fail summary distinguishes that from real closure, which is why the gate audits structure rather than results.
And the second idea is the evidence standard (§4). "Retry is tested" is a review statement. "Retry × sustained backpressure × outstanding depth is covered, the scoreboard correlates semantic identity independently across attempts, and mutation testing proved the checker fails when duplicate delivery is injected" is evidence. This gate accepts only the second, and §24's checklist is written to force it.
2. What This Gate Owns
| Gate | Asks | Not this chapter |
|---|---|---|
| 27.1 — Architecture | what are we building, and who owns each decision? | — |
| 27.2 — RTL | did we implement it safely? | code-level review, lint, X-propagation |
| 27.3 — Verification (this chapter) | can we prove it? | — |
| 27.4 — Performance | can it meet the workload? | measured throughput, counters, margin |
| 27.5 — Integration | can independent pieces operate together? | package seams, interop |
| 27.6 — Debug | can we diagnose it in silicon? | observability, first-failure capture |
| 27.7 — Interview | can an engineer reason through all of it? | — |
Three boundaries, stated because this gate is the one most often merged with its neighbours.
This gate does not re-review RTL. "Is this else if losing an update?" is 27.2 §8. "Would our scoreboard have detected the drift?" is this gate's — and they have different answers surprisingly often.
This gate does not build the environment; 25.8 did that. 25.8 owns the design of a multi-agent UCIe environment — agents, predictors, analysis topology. This chapter owns the audit: given an environment, can it prove the architecture correct? Where 25.8 explains a structure, this gate asks for the evidence that the structure works.
And this gate does not measure performance. A throughput shortfall is 27.4's. But whether the environment can observe a throughput shortfall — whether the counters exist and are sampled — is partly this gate's, and §19 hands that boundary over explicitly.
3. PASS / CONDITIONAL PASS / FAIL
Module 27's gates need decision semantics, and this is where they are defined for the rest of the module.
| Decision | Requires |
|---|---|
| PASS | requirement closed · owner clear · evidence reproducible · risk understood · no unresolved architectural ambiguity |
| CONDITIONAL PASS | known issue · bounded risk · named owner · documented mitigation · closure date or precondition · does not invalidate a downstream gate's assumptions |
| FAIL | unresolved correctness ambiguity · unverified cross-layer contract · target not demonstrated · integration assumption unresolved · insufficient observability for a known risk · the review rests on intention rather than evidence |
Four readings, and the third is the one that keeps a gate honest.
"Evidence reproducible" is the PASS criterion that does the most work. A result someone saw once, on a branch, with a local edit, is not evidence. A named regression, a named test, a coverage report, a mutation result — those are reproducible by someone else, which is what makes a PASS survive the reviewer leaving the company.
A CONDITIONAL PASS has five required fields and is void without any of them. In particular it needs a closure date or precondition and a statement that it does not invalidate a downstream gate's assumptions. A conditional pass on predictor independence, for instance, invalidates every subsequent claim of functional closure — so it is not conditional, it is a FAIL wearing a schedule.
And the last FAIL row is the one reviewers resist using. "The review rests on intention rather than evidence" is a legitimate, sufficient reason to fail a gate (27.1 §4). A gate that only fails on discovered defects will always pass, because an environment that cannot find defects reports none.
One anti-pattern worth naming explicitly. CONDITIONAL PASS must not become a polite FAIL. The test is mechanical: can a downstream gate proceed on the assumption that this item will close? If 27.4 would have to redo its work when the item closes, the item is blocking and the honest decision is FAIL.
4. Statement Versus Evidence
Every item in §24 is written to force the right-hand column.
| Review statement (worthless) | Evidence (accepted) |
|---|---|
| "retry is tested" | retry × sustained backpressure × outstanding-depth crosses covered; scoreboard correlates semantic identity across attempts independently; mutation with injected duplicate delivery FAILED the checker |
| "the scoreboard checks it" | the analysis topology, showing the predictor's input port is not fed by the output monitor (§7) |
| "we have 100 % coverage" | the requirement→coverpoint map, with the crosses that were hit and the ones deliberately excluded, each with a reason |
| "recovery works" | recovery injected with N outstanding obligations; exactly one completion per semantic operation asserted; both retention policies tested |
| "assertions pass" | each assertion's antecedent covered; each proven capable of failing by mutation (27.2 §17) |
| "the test suite is large" | nothing. Test count is not a metric of anything (§23) |
| "DV signed off" | the requirements matrix with a status per row and a named owner per gap (§6) |
Three readings.
The pattern in the right-hand column is always the same three parts: what stimulus was applied, what independently checked it, and what proves the check can fail. An item missing the third part is untested verification — and §22's mutation testing is how you get it.
Row 6 is deliberately blunt. Test count measures effort, not closure. A suite of 20,000 tests against a mirrored predictor (§8) has the same diagnostic power as zero tests, and it costs more to run.
And row 3 matters because coverage is the metric most often misused as evidence. 100 % code coverage says every line executed. It says nothing about whether any line was checked, and nothing at all about combinations (27.2 §3 pass 2's territory, in DV form).
5. Sourcing and Scope
6. Area A — The Verification Plan as a Requirements Matrix
A verification plan is not a list of tests. It is a mapping from architectural requirements to the evidence that closes each one.
| Column | What it must contain | A gap here means |
|---|---|---|
| requirement | one architectural decision from 27.1 | the plan is not traceable |
| stimulus | the test or sequence that creates the condition | untestable requirement |
| checker | the independent mechanism that decides pass/fail | stimulus without a checker is NOT verification |
| coverage | the coverpoint or cross proving it was reached | checker without coverage is not closure |
| fault injection | what negative condition was injected | only happy paths verified |
| expected failure evidence | what a failure would look like | you cannot recognise the bug |
| owner | a name | nobody closes it |
| status | PASS / CONDITIONAL / FAIL (§3) | the plan cannot be reviewed |
Four readings.
The two load-bearing sentences of this whole area: a requirement with stimulus but no checker is not verified — it is exercised. And a requirement with a checker but no coverage is not closed — you have a mechanism that would have caught it, and no evidence the condition ever occurred.
Column 6 is the one nobody writes and every debug session needs. "What would a failure look like?" forces the plan to state the observable — an error class, a scoreboard message, a counter divergence. Without it, a failing regression produces a symptom nobody can map back to a requirement, and triage begins from zero (27.6 inherits this).
Traceability runs backwards to 27.1. Every architecture decision should appear as a row. A decision with no row is unverified by construction, and that is a finding at this gate even though the defect was created at the first one.
And the review technique is to sample rather than read. Take five requirements — including the two 27.1 §19 identifies as reaching silicon most often — and walk all eight columns. If two of five have a gap, the plan has a systematic problem and the finding is about the plan, not the rows.
7. Area B — Predictor Independence
The single highest-value audit in this chapter, and it takes ten minutes.
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| B1 | the predictor's input is not the DUT's output | the actual connect_phase source, read line by line | "it's a reference model" | any path from output monitor into the model |
| B2 | the model does not call DUT functions or import its package | the model's import list and dependencies | "it's a separate file" | shared implementation functions |
| B3 | constants are derived from the spec, not copied from RTL | the derivation, or a \define` from a shared spec header | "the values match" | constants copied from the design |
| B4 | a deliberate DUT mutation causes a failure | the mutation run and its failure signature (§22) | "it would fail" | never demonstrated |
| B5 | coverage is sampled from the monitor, not the model | the covergroup's sampling source | — | model-sampled coverage |
Three readings.
B1 is checked by reading connect_phase, not by asking. Everyone believes their predictor is independent. The topology either has a path from the output monitor into the model or it does not, and that is a fact you can see in a few lines of code (25.8 §11).
B3 is the subtle one and it produces a testbench that agrees with the design's misreadings. If a width, an encoding or a timeout constant is imported from the RTL package, the checker cannot disagree with the design about it — so a misread specification value is confirmed rather than caught. The right source is the specification (recorded per 27.1 §7 B6), or at minimum a shared header owned by neither side.
And B4 converts belief into evidence. "The scoreboard would catch a duplicate" is a review statement (§4). A mutation run that injects duplicate delivery and shows the scoreboard failing with the expected message is evidence — and it is the only thing that distinguishes a working checker from a decorative one.
8. Wrong UVM — the Mirrored Predictor
// WRONG. ILLUSTRATIVE. The analysis topology that produces a permanently green
// regression. Written by someone connecting ports in the order they appear in
// the environment — and every individual connection looks reasonable.
class ucie_env extends uvm_env;
ucie_agent m_in_agent; // drives and monitors the input side
ucie_agent m_out_agent; // PASSIVE monitor on the output side
ucie_predictor m_pred;
ucie_scoreboard m_sb;
function void connect_phase(uvm_phase phase);
// BUG: the predictor is fed from the OUTPUT monitor. The "expected"
// stream is now a function of what the DUT actually produced.
m_out_agent.mon.ap.connect(m_pred.analysis_export);
// The same output monitor also supplies "actual".
m_pred.expect_ap.connect(m_sb.expected_export);
m_out_agent.mon.ap.connect(m_sb.actual_export);
endfunction
endclassArchitecture. Three connections. Two of them originate at the same port — which is the entire defect, and it is invisible unless you trace the sources rather than read the lines.
State. The scoreboard holds expected and actual queues that are, structurally, two views of one stream.
Event. Every output transaction writes both sides.
Contract. A scoreboard's contract is that expected is derived independently. Here it is derived from actual, so the comparison is f(x) == x for a predictor that is close to identity in the fields that matter.
Failure — the timeline. The DUT drops one transaction and duplicates another.
| Event | DUT output | Predictor input | Expected | Actual | Scoreboard |
|---|---|---|---|---|---|
| txn A accepted | A emitted | A | A | A | match |
| txn B accepted | B DROPPED | nothing | nothing | nothing | no comparison at all |
| txn C accepted | C emitted | C | C | C | match |
| txn D accepted | D emitted TWICE | D, D | D, D | D, D | both match |
| end of test | — | — | queues equal | queues equal | PASS |
Two real, severe bugs — a dropped transaction and a duplicate delivery — and the regression is green.
Root cause. The expected value was derived from the thing being checked. A dropped transaction produces no expectation, so nothing detects the absence. A duplicated transaction produces a duplicated expectation, so the duplicate matches.
And the reason it survives review is that the environment looks textbook-complete: agents, a passive monitor, a predictor, a scoreboard, coverage. Every component exists. One wire is wrong.
DV/debug. The signature is the absence of a signature — no failures, ever, including in early bring-up when the design was genuinely broken. A scoreboard that has never failed during development is evidence of a problem, not of quality (27.2 §17). B4's mutation run makes it undeniable in one afternoon.
9. Corrected — Auditable Independence
// CORRECT. ILLUSTRATIVE. The predictor is driven from the INPUT side and from
// configuration only. The topology is written so that independence is visible
// in five lines rather than argued about in a meeting.
class ucie_env extends uvm_env;
ucie_agent m_in_agent;
ucie_agent m_out_agent;
ucie_predictor m_pred;
ucie_scoreboard m_sb;
function void connect_phase(uvm_phase phase);
// EXPECTED path: input stimulus -> predictor -> scoreboard.
// The predictor never observes the output side. This single line is what
// the review is checking, and it should be commented as such.
m_in_agent.mon.ap.connect(m_pred.analysis_export);
m_pred.expect_ap.connect(m_sb.expected_export);
// ACTUAL path: output observation -> scoreboard. Terminates AT the
// scoreboard; it must never fan out to the predictor.
m_out_agent.mon.ap.connect(m_sb.actual_export);
// Coverage samples OBSERVED behaviour, never predicted behaviour (B5) —
// otherwise coverage reports what the model imagined, not what happened.
m_in_agent.mon.ap.connect(m_cov.analysis_export);
endfunction
endclassArchitecture. Two disjoint paths that meet only inside the scoreboard. The property being reviewed — no edge from the output monitor into the model — is now a structural fact.
State. Unchanged; the fix is topological, not behavioural.
Event. The predictor advances on input acceptance, so it produces an expectation whether or not the DUT emits anything — which is precisely what makes a dropped transaction detectable (§14's orphan and incomplete checks).
Contract. The predictor must model the architecture from the specification and configuration, never from observed output. Its inputs are stimulus plus config; that is the whole allowed set.
Failure. Replay §8's timeline against this topology: B produces an expectation with no actual → SB_INCOMPLETE at end of test. D produces one expectation and two actuals → SB_ORPHAN on the second. Both bugs are now two distinct, named failures.
DV/debug. The review artefact is this connect_phase with the comments. A reviewer should be able to confirm independence without reading the predictor at all — and if confirming it requires reading the predictor, that is itself a finding.
10. Area C — Monitor Acceptance Semantics
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| C1 | the monitor samples acceptance, not offering | the sampling expression: valid && ready | "it uses a clocking block" | samples valid alone |
| C2 | signals are sampled through a clocking block | the clocking block declaration and skew | direct interface reads | race-prone sampling |
| C3 | the transaction is cloned before write() | the clone/copy call | — | subscribers share a handle |
| C4 | observed transport facts are excluded from comparison | UVM_NOCOMPARE on attempt/link fields | — | retries reported as mismatches |
| C5 | the monitor is passive and drives nothing | the agent's is_active setting | — | a monitor with a driver |
Three readings.
C1 is 27.2 §6's question asked of the testbench, and the consequence here is worse. In RTL a valid-only counter inflates a metric. In a monitor it publishes the same transaction once per stalled cycle — so the scoreboard sees duplicates that the design never produced. The testbench then reports a DUT bug that does not exist, and a team can spend a week proving the RTL innocent.
C3 is a one-call defect with a nasty signature. If the monitor reuses one transaction object and writes a handle, every subscriber sees the last transaction, and earlier ones appear mutated. The symptom is a scoreboard mismatch whose data changes when you add a second subscriber — which sends debug toward the scoreboard and away from the monitor.
And C4 keeps the layers separate. Transport facts — attempt count, which physical link — belong in the transaction for debug and must not participate in comparison (25.5 §11). Otherwise a legitimate retry is a mismatch, and the team learns to ignore mismatches.
11. Wrong Monitor — Sampling the Offer
// WRONG. ILLUSTRATIVE. The monitor publishes whenever valid is high. Under
// backpressure the producer HOLDS valid with the same payload, so one
// transaction is published once per stalled cycle.
task ucie_monitor::run_phase(uvm_phase phase);
ucie_txn t;
forever begin
@(cb);
if (cb.valid) begin // BUG 1: no ready term
t = ucie_txn::type_id::create("t");
t.sem_id = cb.sem_id;
t.data = cb.data;
ap.write(t); // BUG 2: publishes every stalled cycle
end
end
endtaskArchitecture. One sample, one publish. Correct if ready is always high — which is exactly the condition under which nobody exercises backpressure.
State. None retained, so the monitor cannot tell a held offer from a new one.
Event. Every clock edge with valid high.
Contract. Subscribers assume one write() per transaction. The monitor's contract is broken for the entire duration of any stall.
Failure — the timeline. One transaction, ready low for four cycles.
| Cycle | valid | ready | Transfer | Monitor publishes | Scoreboard sees |
|---|---|---|---|---|---|
| 10 | 1 | 0 | no | txn(A) | 1 × A |
| 11 | 1 | 0 | no | txn(A) | 2 × A |
| 12 | 1 | 0 | no | txn(A) | 3 × A |
| 13 | 1 | 0 | no | txn(A) | 4 × A |
| 14 | 1 | 1 | YES | txn(A) | 5 × A |
| — | — | — | — | — | 4 spurious duplicates |
Root cause. A testbench bug that accuses the RTL. The design transferred A exactly once; the monitor reported it five times, and the scoreboard raises four duplicate-delivery errors against innocent logic.
And the damage compounds. The failure appears only under backpressure, so it correlates with load — which looks exactly like a real congestion-related design bug. Teams have chased this into the RTL for days.
Corrected.
// CORRECT. Publish on the ACCEPTED event, and clone so subscribers cannot
// observe a mutated object (C3).
task ucie_monitor::run_phase(uvm_phase phase);
ucie_txn t, t_pub;
forever begin
@(cb);
if (cb.valid && cb.ready) begin // the transfer, not the offer
t = ucie_txn::type_id::create("t");
t.sem_id = cb.sem_id;
t.generation = cb.generation;
t.data = cb.data;
t.attempt = cb.attempt; // carried for debug, NOT compared
$cast(t_pub, t.clone()); // independent object per publish
ap.write(t_pub);
end
end
endtaskDV/debug. The review question is one line long: "show me the sampling expression." And the corroborating evidence is a coverage bin on transactions accepted while a stall was in progress — if that bin is empty, backpressure was never exercised and C1 has never actually been tested either way.
12. Area D — Scoreboard Architecture
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| D1 | matching is by semantic identity, not arrival order | the outstanding map keyed by sem_id | "it's in order" | FIFO compare where ordering is not contractual |
| D2 | generation is checked | the generation comparison | — | stale completion retires a live entry |
| D3 | orphan actuals are an error | a named SB_ORPHAN error | silently dropped | unexpected output ignored |
| D4 | unmatched expecteds are an error at end of test | SB_INCOMPLETE in check_phase | — | dropped transactions invisible |
| D5 | the "nothing was checked" case is an error | SB_NOTHING_CHECKED | — | an empty run reports PASS |
| D6 | duplicate delivery is distinguishable from mismatch | distinct error classes | one generic error | triage cannot start |
Three readings.
D1 is where a scoreboard silently narrows its own scope. FIFO comparison is correct only if ordering is contractual (27.1 §7 B-area). Where it is not, an out-of-order but perfectly legal response is a false failure — and the usual repair is to relax the check, which removes the ability to detect real reordering.
D4 and D5 are the two checks that make a quiet environment fail loudly. Without D4, a dropped transaction is simply an expectation nobody looked at. Without D5, a test whose stimulus never started reports PASS with a spotless scoreboard — the most dangerous single result in verification, because it looks like the best one.
And D6 is a triage requirement more than a correctness one. SB_ORPHAN, SB_STALE_GEN, SB_REALLOC and SB_MISMATCH name four different design bugs (25.8 §14). A single "scoreboard error" tells you a problem exists and nothing about which subsystem to open.
13. Illustrative UVM — the Scoreboard
// ILLUSTRATIVE (§8). Architecture only; factory boilerplate elided. The
// structure being reviewed is: identity-keyed outstanding map, generation
// check, four distinct error classes, and a check_phase that can FAIL a
// silent run.
class ucie_scoreboard extends uvm_scoreboard;
// Expected transactions, keyed by SEMANTIC identity — not by arrival order.
ucie_txn m_expected [int];
int m_live_gen [int]; // generation currently live per id
int unsigned m_checked; // how many comparisons happened
// EXPECTED side — fed only from the input path (§9).
function void write_expected(ucie_txn t);
if (m_expected.exists(t.sem_id)) begin
// The id was reallocated while an expectation was still outstanding.
// This is a DUT-visible protocol error, not a testbench artefact.
`uvm_error("SB_REALLOC",
$sformatf("sem_id %0d reallocated while live (gen %0d -> %0d)",
t.sem_id, m_live_gen[t.sem_id], t.generation))
end
m_expected[t.sem_id] = t;
m_live_gen[t.sem_id] = t.generation;
endfunction
// ACTUAL side — fed only from the output monitor.
function void write_actual(ucie_txn a);
ucie_txn e;
if (!m_expected.exists(a.sem_id)) begin
// Output for something never requested, or for an already-retired id.
`uvm_error("SB_ORPHAN",
$sformatf("actual sem_id %0d has no live expectation", a.sem_id))
return;
end
if (a.generation != m_live_gen[a.sem_id]) begin
// A completion from a PREVIOUS use of this id. Retiring on sem_id alone
// here would silently retire the live transaction (D2).
`uvm_error("SB_STALE_GEN",
$sformatf("sem_id %0d: actual gen %0d != live gen %0d",
a.sem_id, a.generation, m_live_gen[a.sem_id]))
return;
end
e = m_expected[a.sem_id];
// compare() honours UVM_NOCOMPARE on observed transport fields (C4), so a
// retry does not read as a data mismatch.
if (!a.compare(e)) begin
`uvm_error("SB_MISMATCH",
$sformatf("sem_id %0d payload mismatch", a.sem_id))
end
m_checked++;
m_expected.delete(a.sem_id); // retire only on a matched, live, in-gen actual
endfunction
function void check_phase(uvm_phase phase);
// Every expectation that never arrived. Without this, a DROPPED
// transaction is simply an entry nobody looked at (D4).
foreach (m_expected[id])
`uvm_error("SB_INCOMPLETE",
$sformatf("sem_id %0d expected but never observed", id))
// The most important four lines in the class: a run that checked NOTHING
// must FAIL. Otherwise a testbench whose stimulus never started reports a
// clean pass, which is the most dangerous result available (D5).
if (m_checked == 0)
`uvm_error("SB_NOTHING_CHECKED",
"scoreboard performed zero comparisons — stimulus or connectivity broken")
endfunction
endclassArchitecture. An associative map keyed by semantic identity, a live-generation map beside it, and four named errors plus two end-of-test errors. Every element exists to make one specific bug distinguishable from the others.
State. m_expected holds outstanding expectations; m_live_gen records which use of each identifier is current; m_checked exists solely so a silent run can fail.
Event. Expectations are created on input acceptance; retirement requires an actual that is present, in-generation and matching. Three conditions, because failing any one of them is a different bug.
Contract. The scoreboard assumes the monitor publishes once per accepted transaction (§11) and that transport fields are excluded from comparison (C4). Both assumptions are review items in their own right — a scoreboard is only as trustworthy as the monitors feeding it.
Failure. Remove the generation check and a late completion from a recycled identifier retires a live transaction — the environment then reports a spurious SB_INCOMPLETE for the real transaction, sending debug toward a drop that never happened.
DV/debug. m_checked should be reported at end of test even when the run passes. A regression whose comparison count silently falls by 90 % after a refactor is broken, and the pass/fail summary will never tell you.
14. Area E — Reset and Recovery Under Live Traffic
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| E1 | recovery is injected with obligations OUTSTANDING | the test, plus the outstanding count at injection | "we test recovery" | only idle recovery tested |
| E2 | the retention policy is asserted | the property from 27.1 §16 | — | policy untested either way |
| E3 | exactly one completion per semantic operation across recovery | the assertion and its coverage | — | duplicate delivery undetected |
| E4 | reset scopes are tested separately | a test per scope (26.5 §7) | one "reset test" | scope confusion undetected |
| E5 | first-fault capture survives the reset under test | the captured value after reset | — | evidence destroyed (27.6) |
| E6 | back-to-back and overlapping recoveries are exercised | the coverage bin | — | second event dropped |
Three readings.
E1 is the item that would have caught 26.5 §21's flagship integration failure, and it is almost always missing. A directed recovery test naturally drains the pipeline first — it is easier to write, easier to debug and completely blind to the bug. The review question is quantitative: "what was the outstanding count at the moment of injection?" If the answer is zero, the test proves nothing about retention.
E1 plus E2 together are what make a retention policy verified rather than assumed. Both policies are implementable (27.1 §15); the environment must assert the one that was chosen, and it must have been chosen.
And E6 exists because the second fault is the realistic one. A link that is recovering is a link that is marginal — so a second error during recovery is not an exotic corner, it is the expected environment. If the design drops the second event, it believes it is recovering and is not.
15. Area F — Fault Injection at Contract Boundaries
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| F1 | faults are injected where a contract exists | the boundary list, mapped to §6's requirements | random bit flips | injection unrelated to contracts |
| F2 | sustained backpressure, not a single stall | the stall-duration coverage bins | one-cycle stalls | §11's monitor bug never exposed |
| F3 | delayed and out-of-order completion | the delay-distribution config | fixed latency | ordering assumptions untested |
| F4 | stale response after retirement | the injected scenario | — | SB_ORPHAN never exercised |
| F5 | credit starvation to zero | the coverage bin at zero credits | — | boundary never reached |
| F6 | configuration change with traffic in flight | the test | config changes at idle | 26.5 §17's window untested |
| F7 | injection is legal for the interface | the injection's justification | — | fabricated wire errors |
Three readings.
F7 is a discipline item and it belongs in a UCIe chapter specifically. Do not fabricate protocol-level error conditions whose legality you have not established (§5). Inject at boundaries you own — backpressure, delay, credit starvation, configuration timing, reset scope — all of which are legal, realistic, and where the interesting bugs are.
F2 is the pairing that makes F-area worth running. A single-cycle stall exercises the stall path; sustained backpressure exercises everything downstream of it — the monitor (§11), the credit accounting, the outstanding window, the arbiter's fairness ageing. One-cycle stalls are why §11's bug reaches silicon.
And F4 is how you prove D3 is not decorative. SB_ORPHAN must be observed to fire at least once, in a directed test that injects a stale response. An error class that has never triggered is an untested branch of the testbench — and testbench code is code (27.2 §1).
16. Area G — Concurrency Crosses
The most common systematic gap in a mature DV plan: every feature tested, no two tested together.
| Cross | Why it matters | FAIL if |
|---|---|---|
| backpressure × retry | retry under a full pipeline is where identity confusion appears | not covered |
| retry × recovery | a retry in flight when recovery starts | not covered |
| config change × live traffic | 26.5 §17's split-configuration window | not covered |
| high outstanding × backpressure | the window and the credits interact | not covered |
| error × bidirectional traffic | both directions loaded during a fault | not covered |
| reset × pending work | §14 E1 — the retention question | not covered → gate FAIL |
| recovery × recovery | E6's second event | not covered |
| credit exhaustion × recovery | credits at zero when state is rebuilt | not covered |
Three readings.
The review question is not "do you have random tests?" Constrained-random stimulus can reach these crosses and frequently does not, because the constraints that make a test converge also make rare combinations rare. The evidence that counts is the cross coverage bin, not the presence of randomisation.
Row 6 is a gate-failing item on its own. Reset × pending work is 26.5 §21's failure and 27.1 §15's two-readings problem. An environment that has never injected reset with work outstanding cannot claim to have verified the retention contract, whatever its test count.
And a practical technique: if a cross is genuinely unreachable under the current constraints, write a directed test rather than declaring it covered. Then the bin is hit by construction, and the test documents the scenario for whoever inherits it.
17. Area H — Coverage That Means Something
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| H1 | coverage is of contracts, not lines | the requirement→coverpoint map (§6) | "100 % code coverage" | code coverage offered as closure |
| H2 | outstanding depth is binned | bins at 0, 1, mid, max−1, max | a single "outstanding" point | window boundary never reached |
| H3 | state and transition coverage on key FSMs | the transition bins, including illegal-attempt bins | state coverage only | transitions unexercised |
| H4 | negative conditions are covered | bins for injected faults (§15) | — | only happy paths measured |
| H5 | crosses from §16 are bins, not aspirations | the cross coverage report | — | crosses absent |
| H6 | exclusions have written reasons | the exclusion file with justifications | blanket exclusions | unexplained exclusions |
| H7 | coverage is sampled from observation | the covergroup's source (B5) | model-sampled | measures imagination |
Three readings.
H1 is the headline and it is worth being blunt about. 100 % code coverage means every line executed. It does not mean any line was checked, and against §8's mirrored predictor it is achievable with a scoreboard that cannot fail. Code coverage is a necessary hygiene metric and is not evidence of verification.
H2's max bin is the one that catches 27.1 §17's concurrency defect from the DV side. If the outstanding depth never reaches its maximum in any test, the design has never been driven hard enough to expose either the window limit or the allocator's behaviour at full.
And H6 turns exclusions from a loophole into a review artefact. An exclusion with a written reason is a decision; an exclusion without one is a hidden gap. The reviewer's job is to read the reasons, not to check the percentage.
18. Area I — Assertion Review, From the DV Side
27.2 §16 reviews assertions as code. This gate reviews them as evidence.
| # | Must be true | Evidence that counts | Weak evidence | FAIL if |
|---|---|---|---|---|
| I1 | every assertion's antecedent is covered | a cover property result per assertion | "it's in the regression" | vacuous assertions counted as closure |
| I2 | each critical assertion has been proven able to fail | the mutation result (§22) | — | never demonstrated |
| I3 | disable iff does not mask the interesting state | the disable expression, read | — | disabled when it matters (27.2 §17) |
| I4 | bounded eventualities have architectural bounds | the bound's derivation | ##[1:$] | cannot fail in finite simulation |
| I5 | assumptions used in formal are justified | the assumption list and their rationale | — | proof of a design that cannot exist |
| I6 | assertion count is not offered as closure | — | "we have 200 assertions" | count presented as evidence |
Two readings.
I1 is the item that converts an assertion count into an assertion result. A property whose antecedent never occurred has passed vacuously and proved nothing. The evidence is a cover result, and it is cheap: one cover property per non-trivial assertion.
And I5 is the highest-risk item in formal work. An over-constraining assumption can make a property provable about a design that cannot exist — the proof is real and the relevance is zero. Every assumption needs a one-line justification, and the reviewer reads those rather than the proof log.
19. Area J — Formal, and Where It Fits
| Property | Formal suitability | Why |
|---|---|---|
| no ID reuse while live | strong | small state, deep bug (26.5 §9) |
| no double allocation | strong | bounded, local |
| credit bounds and conservation | strong | arithmetic invariant (25.6 §10) |
| atomic configuration commit | strong | a few states (26.5 §17) |
| FSM transition legality | strong | 27.2 §12's illegal encodings |
| no duplicate semantic completion | good, if identity state is bounded | may need abstraction |
| end-to-end throughput | NOT a formal property | it is a performance claim — 27.4 |
| full protocol conformance | impractical at scale | complexity |
Three readings.
Rows 1–5 share a shape worth recognising: bounded state, deep consequence, hard to hit by simulation. Those are exactly the properties where formal earns its cost — and all five are the properties whose violations produce silent corruption rather than visible failure.
Row 7 is a boundary that gets crossed in review meetings. Formal cannot prove a throughput target. It can prove there is no deadlock and no state in which forward progress becomes impossible — which is valuable and is not a performance result (27.4 owns that).
And the review item is scoping, not adoption. "We use formal" is a review statement. "These five properties are proven, with these assumptions, and here is why each assumption holds" is evidence (I5).
20. Area K — What "Test Finished" Means
| Accepted definition of done | Verdict |
|---|---|
| the sequence ended | not sufficient |
item_done() returned | not sufficient — driver-level, not semantic |
| objections dropped after the last item | not sufficient — 25.8 §16 |
| semantic outstanding == 0 | sufficient |
| an explicitly defined drain condition, with a bounded timeout | sufficient |
Three readings.
item_done() means the driver finished driving. It does not mean the protocol finished. A test that ends there kills the simulation with transactions in flight — and the scoreboard's SB_INCOMPLETE entries are then indistinguishable from real drops (25.8 §16).
The drain must be bounded. An unbounded wait for outstanding == 0 hangs the regression when the design genuinely drops something — converting a clean failure into a timeout with no diagnosis. fork/join_any with a named DRAIN_TIMEOUT error is the shape, and the timeout must be a distinct error class from the drop it is detecting.
And the review question is one sentence: "what exactly is your end-of-test condition, and what error fires if it is not reached?" If the answer is "the sequence ends", every SB_INCOMPLETE in the regression is uninterpretable.
21. Area L — Debug Evidence From the Environment
A partial hand-off to 27.6, owned here because it is a testbench property.
| # | Must be true | FAIL if |
|---|---|---|
| L1 | a failure names the requirement, not just the signal | triage starts from zero |
| L2 | the transaction's semantic ID, generation and attempt are in the message | cannot correlate across components |
| L3 | scoreboard errors are distinct classes (D6) | one generic error |
| L4 | the comparison count is reported on pass as well as failure | a silent regression drift goes unnoticed |
| L5 | failing seeds are reproducible | "it failed once" is not evidence |
And L5 is a PASS/FAIL item in its own right. A failure that cannot be reproduced cannot be closed, and a regression whose seeds are not recorded produces exactly that. This is cheap infrastructure and it is the difference between a finding and a rumour.
22. Area M — Mutation Testing
The strongest single item in this gate, because it is the only one that tests the testbench.
| Injected defect | The environment must fail with |
|---|---|
| duplicate delivery of one transaction | SB_ORPHAN |
| drop one transaction | SB_INCOMPLETE at end of test |
| complete with a stale generation | SB_STALE_GEN |
| reallocate an id while live | SB_REALLOC |
| corrupt one payload byte | SB_MISMATCH |
| skip one credit return | the credit-conservation assertion |
| commit a config change mid-transaction | the epoch assertion (26.5 §17) |
| clear semantic state on link reset | the retention assertion (E2) |
Three readings, and this section is what turns §4's right-hand column from an aspiration into a procedure.
Each row must fail for the expected reason. A mutation that fails with a different error is nearly as bad as one that passes — it means the environment detects that something is wrong and misattributes it, which produces a debug session pointed at the wrong subsystem.
A mutation that does not fail at all is a hole in the environment, and it is a FAIL for that requirement regardless of how much coverage it has. This is the single test that would have caught §8's mirrored predictor in one afternoon — inject a dropped transaction, observe a clean pass, and the topology bug is undeniable.
And the review evidence is the mutation report, not the assertion that mutations were run. Eight rows, eight expected error classes, eight observed results. That table is what a PASS in this area looks like.
23. Flagship Review Failure — Twenty Thousand Green Tests
The scenario that defines this gate.
What the team presents. 20,000 tests in the nightly regression. 100 % statement, branch and toggle coverage. Zero failures for six weeks. A verification plan with every row marked complete. Every conventional signal says PASS.
What the review does. Reads connect_phase (§7 B1) — fifteen lines, ten minutes.
What it finds. The predictor's analysis_export is connected to the output monitor (§8).
| Question asked | Answer | Consequence |
|---|---|---|
| Where does expected come from? | the output monitor | the scoreboard compares the DUT to itself |
| Has the scoreboard ever failed? | not since week 2 of bring-up | consistent with a checker that cannot fail |
| Has a mutation been run? | no | no evidence the checker works |
| What would a dropped transaction produce? | nothing — no expectation is created | drops are invisible |
| What would a duplicate produce? | two matching pairs | duplicates are invisible |
Gate decision: FAIL.
And the reasoning is worth stating precisely, because it is counter-intuitive.
The test count has zero value here. Not low value — zero. 20,000 runs of a checker that cannot fail produce exactly as much evidence as 0 runs, and consume six weeks of compute. Effort is not evidence (§4 row 6).
The coverage number has zero value for the same reason. 100 % code coverage against a mirrored predictor means every line was executed; nothing was checked. Coverage measures stimulus reach, and this gate is about checking.
And this is not a CONDITIONAL PASS, by §3's mechanical test. Could a downstream gate proceed assuming this closes? No — 27.4's performance claims, 27.5's interop evidence and every functional signoff downstream rest on the assumption that functional bugs would have been caught. The item invalidates downstream gates, so it is blocking, so it is FAIL.
The remediation is also worth naming, because "fix the connection" is not sufficient. Reconnect (§9), then re-run the full regression and expect failures — six weeks of unchecked design changes are now under examination for the first time. A team that reconnects the predictor and still sees zero failures has a second problem.
24. The Verification Gate Checklist
Thirty-eight items. Each has a section with the reasoning; this is the sheet you take into the room.
Plan and traceability
| # | Question | FAIL if |
|---|---|---|
| 1 | Does every 27.1 decision appear as a requirement row? | a decision has no row (§6) |
| 2 | Does every requirement have a checker, not just stimulus? | stimulus-only rows exist |
| 3 | Does every requirement have coverage proving it was reached? | checker without coverage |
| 4 | Does every row state what a failure would look like? | column absent |
| 5 | Does every open row have a named owner and a status? | unowned gaps |
| 6 | Sample five rows end to end — do all eight columns hold? | two of five have gaps |
Predictor and topology
| # | Question | FAIL if |
|---|---|---|
| 7 | Where does expected originate — read connect_phase | any path from the output monitor into the model |
| 8 | Does the model import DUT packages or call DUT functions? | shared implementation |
| 9 | Are checker constants derived from the spec, not copied from RTL? | constants copied from the design |
| 10 | Has a DUT mutation been shown to fail the checker? | never demonstrated |
| 11 | Is coverage sampled from observation, not from the model? | model-sampled |
Monitors
| # | Question | FAIL if |
|---|---|---|
| 12 | Does the monitor sample valid && ready? | samples the offer (§11) |
| 13 | Is sampling through a clocking block with defined skew? | direct interface reads |
| 14 | Is the transaction cloned before write()? | subscribers share a handle |
| 15 | Are observed transport fields excluded from comparison? | retries read as mismatches |
| 16 | Is there a coverage bin for acceptance after a stall? | backpressure never exercised |
Scoreboard
| # | Question | FAIL if |
|---|---|---|
| 17 | Is matching by semantic identity rather than arrival order? | FIFO compare without an ordering contract |
| 18 | Is generation checked before retirement? | stale completion retires a live entry |
| 19 | Is an orphan actual an error? | silently dropped |
| 20 | Is an unmatched expectation an error at end of test? | drops invisible |
| 21 | Does a run with zero comparisons FAIL? | an empty run reports PASS |
| 22 | Are orphan / stale / realloc / mismatch distinct classes? | one generic error |
Reset, recovery and faults
| # | Question | FAIL if |
|---|---|---|
| 23 | Was recovery injected with obligations outstanding? | only idle recovery tested |
| 24 | What was the outstanding count at injection? | the answer is zero |
| 25 | Is the retention policy asserted, in the direction chosen? | untested either way |
| 26 | Is exactly one completion per semantic operation asserted across recovery? | duplicates undetected |
| 27 | Are the reset scopes tested separately? | one generic reset test |
| 28 | Is sustained backpressure exercised, not single-cycle stalls? | one-cycle stalls only |
| 29 | Is credit starvation to zero reached? | boundary never hit |
| 30 | Is fault injection confined to legal, owned boundaries? | fabricated protocol errors |
Concurrency, coverage and assertions
| # | Question | FAIL if |
|---|---|---|
| 31 | Are §16's crosses coverage bins with results? | crosses aspirational |
| 32 | Is reset × pending work covered? | not covered → gate FAIL |
| 33 | Is outstanding depth binned including max? | window boundary unreached |
| 34 | Do exclusions carry written reasons? | blanket exclusions |
| 35 | Is every assertion's antecedent covered? | vacuous assertions counted |
| 36 | Are formal assumptions justified individually? | proof of an impossible design |
Closure
| # | Question | FAIL if |
|---|---|---|
| 37 | What is the end-of-test condition, and what error fires if unmet? | "the sequence ends" |
| 38 | Is there a mutation report with expected and observed error classes? | mutations never run |
And the review technique matters as much as the list. Items 7, 10, 21, 23 and 38 are the five that most often change a gate decision, and they take under an hour combined. Run those first; if any of them fails, the remainder is diagnostic detail rather than a decision.
25. How the Verification Review Itself Fails
| Failure mode | Looks like | Why it is fatal |
|---|---|---|
| reviewing results instead of structure | reading the regression report | §23 — a green report from a broken checker |
| accepting counts | "20,000 tests, 200 assertions" | effort is not evidence (§4) |
| accepting code coverage as closure | a 100 % chart | measures stimulus reach, not checking |
| reviewing the plan document, not the code | every row marked complete | the document cannot show connect_phase |
| conditional-passing a blocking item | schedule preserved | §3 — a FAIL wearing a date |
Two readings.
Row 4 is the one that lets §23 happen. A verification plan is a claim; connect_phase is a fact. A review that never opens the source code is reviewing the claim — and every environment's documentation says the predictor is independent.
And row 5 is the failure that compounds. A conditional pass on predictor independence lets 27.4, 27.5 and every functional signoff proceed on an assumption that is false. When it eventually closes, all of that work is invalidated — which is exactly the test §3 gives for whether an item is blocking.
26. Red Flags
| Heard in review | Usually means | Ask |
|---|---|---|
| "we have 20,000 tests" | structure was never audited | "has the scoreboard ever failed?" |
| "100 % code coverage" | code coverage offered as closure | "which contract crosses are covered?" |
| "the scoreboard checks it" | B1 unverified | "show me connect_phase" |
| "it's a reference model" | independence assumed | "what feeds its analysis export?" |
| "the values match the RTL" | B3 — constants copied | "derived from what?" |
| "we test recovery" | idle recovery only | "what was outstanding at injection?" |
| "random hits it eventually" | crosses not binned | "show me the cross bin" |
| "assertions all pass" | possibly vacuous | "is each antecedent covered?" |
| "the sequence completes" | no semantic drain | "what error fires if it does not drain?" |
| "it would catch that" | no mutation evidence | "has it ever caught that?" |
| "we'll close it next month" | possible blocking item | "can 27.4 proceed assuming this closes?" |
And the single most useful question in the room is the third one. "Show me connect_phase" takes ten minutes, requires no preparation, and decides §23's gate outright.
27. Common Misconceptions
"A large regression is strong evidence." §23: 20,000 runs of a checker that cannot fail carry zero evidence.
"100 % code coverage means we're done." §17 H1: it means every line executed. Nothing about whether anything was checked.
"Our predictor is independent — it's a separate class." §7 B1: independence is a property of the topology, not of file organisation. Read connect_phase.
"The scoreboard has never failed, so the design is clean." §8: that is equally consistent with a scoreboard that cannot fail. Mutation testing distinguishes them.
"The monitor uses a clocking block, so sampling is fine." §10 C1: a clocking block fixes races, not semantics. valid alone still publishes duplicates under stall.
"A duplicate-delivery error means the RTL is broken." §11: it frequently means the monitor is. Check the sampling expression before opening the design.
"We test recovery." §14 E1: with what outstanding? Idle recovery proves nothing about retention.
"Formal proves the design is correct." §19: formal proves the properties you wrote, under the assumptions you made. Unjustified assumptions can prove things about a design that cannot exist.
"The test finished, so everything completed." §20: item_done() is a driver event. Semantic completion is a different fact.
"Conditional pass keeps us on schedule." §3: if a downstream gate cannot proceed on the assumption, it is a FAIL — and conditional-passing it invalidates their work too.
28. Understanding Check
29. Summary
Seven things.
Completeness is coverage of contracts, not code (§1, §17). 100 % code coverage means every line executed — nothing about whether anything was checked.
Independence is a property of the topology (§7–§9). Read connect_phase. A path from the output monitor into the predictor makes the scoreboard compare the DUT to itself, and both drops and duplicates become invisible.
Monitors must sample acceptance (§10–§11). valid alone publishes one transaction per stalled cycle — a testbench bug that accuses the RTL, and only under load.
A scoreboard needs four named errors plus two end-of-test checks (§12–§13), including the one that makes a run with zero comparisons FAIL — because a silent environment otherwise reports the cleanest possible pass.
Recovery must be injected with work outstanding (§14, §16). "What was the outstanding count at injection?" — if zero, the retention contract is unverified.
Mutation testing is the only item that tests the testbench (§22). Eight defects, eight expected error classes, eight observed results. That table is what a PASS looks like.
And the gate has three decisions, not two (§3). CONDITIONAL PASS requires a bounded risk, a named owner, a closure date and no invalidation of a downstream gate — otherwise it is a FAIL with a schedule attached.