UCIe · Module 25
Senior Verification
Designing a UVM environment for a UCIe subsystem — why an environment only checks anything when the expected value comes from somewhere other than the thing being checked, how a mirrored predictor passes every test while verifying nothing, and the scoreboard structure that separates orphan, stale-generation, reallocation and mismatch into four distinct failures.
Chapter 25.7 ended on a verification plan. This is the question that plan invites — and it is a construction question, not an explanation question.
1. What They Ask
"Design a UVM verification environment for a UCIe subsystem."
Or: "How would you verify this?" · "Where do your checkers go?" · "How do you know a transaction actually completed?"
They will hand you a pen. The answer is a topology you can draw, defend boundary by boundary, and then break on purpose.
2. The One-Sentence Model
A verification environment is only checking anything if the expected value is derived from something other than the thing being checked. Everything else in the topology — agents, monitors, ports, phases — exists to make that independence possible and observable.
And the corollary that carries the chapter: a scoreboard that builds its expectation from the DUT's output always agrees, runs green forever, and verifies nothing (§7).
3. What They Are Really Testing
Not whether you can draw sequencer → driver → DUT → monitor. That is junior.
| They are checking | The tell |
|---|---|
| decomposition | you name agents by boundary, not by block |
| active vs passive ownership | you say which agents drive and which only observe |
| predictor independence | §7 — the single biggest signal |
| semantic vs transport checking | two models, one correlation layer (§5) |
| do you know when a test may end? | §14 — item_done() is not completion |
| recovery under live traffic | §16 — not idle-inject-pass |
| coverage that means something | §19 — crosses, not toggles |
And the third row decides the interview. "The predictor has to be fed from the input side" is one sentence, and a candidate who says it unprompted has done this work.
4. What You Can Safely Assert
5. The Environment
Four things to read, and the first is the whole diagram.
No edge runs from the output monitor into a model. The output monitor feeds only the scoreboard's actual side. That absence is §7, and it is what makes the environment a checker rather than a mirror.
Three active agents, three passive monitors, and the monitors sit at boundaries — protocol input, adapter, link — rather than at blocks. Boundaries are where contracts live (25.3 §9).
Two models, because there are two questions (§11). Did the protocol behave correctly? Did the transport move it correctly? A single model conflates failures that go to different teams (25.5 §17).
And the reset controller reaches the scoreboard, not just the agents — because a recovery changes what the scoreboard should expect, and a controller that only pokes the DUT leaves the model behind (§16).
6. Agent Roles
| Agent | Active? | Drives | Why it exists |
|---|---|---|---|
| protocol | active | semantic requests at the protocol boundary | the primary stimulus |
| sideband / config | active | configuration, negotiation, mode changes | §15 — config changes under traffic |
| fault injection | active | integrity faults, stalls, delayed completions | §17 |
| peer / BFM | active or passive | the far end, if the DUT is one side only | lets you test before the peer die exists |
| protocol-input monitor | passive | — | feeds the semantic model (§9) |
| adapter monitor | passive | — | objects and attempts |
| link monitor | passive | — | physical attempts, recovery events |
And the peer-BFM row is worth raising unprompted (24.3 §16): the peer die usually does not exist yet, so the environment must be able to run against a contract-derived model of it rather than against the other team's RTL.
7. The Central Principle
WRONG — the predictor reconstructs expectation from the DUT's output.
DUT output ──> output monitor ──> predictor ──> expected queue
│ │
└──────────> actual ─────────────┘
compare
It always matches. The test is green forever, and nothing is verified.
CORRECT — the predictor is fed from the INPUT side.
stimulus ──> input monitor ──> INDEPENDENT model ──> expected
│
DUT output ──> output monitor ──> actual ───────────────┘
compareFour properties, and this is the sentence to say aloud.
It is a topology bug, not a logic bug, so it survives every code review of the predictor itself — the predictor may be perfectly written and still be fed the wrong thing.
It fails silently and permanently. There is no symptom: the regression is green, coverage climbs, and the environment has been checking nothing since the day it was built.
The variant that is harder to spot is a predictor that calls a DUT function to decide what to expect (21.6 §24). The model then shares the design's misunderstanding by construction — and if the specification was ambiguous, both are wrong together.
And the interview phrasing: "The expected value has to come from somewhere the DUT didn't produce — otherwise the comparison is a tautology."
8. UVM — The Transaction
// ILLUSTRATIVE ONLY. The transaction the environment reasons about. Note the
// separation of SEMANTIC fields from OBSERVED transport facts — one object
// carrying both is fine here because this is a MODEL object, not RTL (25.3 §12).
class ucie_txn extends uvm_sequence_item;
// --- SEMANTIC: what the protocol asked for. Stable across every retry. ---
rand bit [15:0] sem_id;
bit [7:0] generation; // which USE of that id (25.4 §12)
rand bit [3:0] op_class;
rand bit [3:0] traffic_class;
rand int length;
// --- CONTEXT: what was true when it was accepted ---
bit [7:0] cfg_epoch; // 25.6 §17
time accept_time;
// --- OBSERVED TRANSPORT FACTS: filled in by monitors, never randomised ---
int attempts; // physical attempts seen
bit completed;
time complete_time;
`uvm_object_utils_begin(ucie_txn)
`uvm_field_int(sem_id, UVM_ALL_ON)
`uvm_field_int(generation, UVM_ALL_ON)
`uvm_field_int(op_class, UVM_ALL_ON)
// attempts/completed are OBSERVATIONS, excluded from compare so that a
// retry does not make two otherwise-identical transactions differ.
`uvm_field_int(attempts, UVM_ALL_ON | UVM_NOCOMPARE)
`uvm_field_int(completed, UVM_ALL_ON | UVM_NOCOMPARE)
`uvm_object_utils_end
function new(string name = "ucie_txn"); super.new(name); endfunction
endclassArchitecture. One model object carrying the semantic identity, the context under which it was accepted, and the transport facts observed about it.
State. Fields only; the object has no behaviour. Lifetime is owned by whoever holds it — §10's clone discipline.
Event. Constructed by a monitor at acceptance (§9), then annotated as attempts and completion are observed.
Contract. attempts and completed are marked UVM_NOCOMPARE deliberately. They are observations, not intent — and comparing them would make a transaction that was retried differ from an otherwise identical one, turning a working retry mechanism into a scoreboard mismatch (25.3 §10).
Failure. Randomising generation rather than assigning it from the allocator's state produces collisions between a live transaction and a new one — §13's stale-generation case, injected by the testbench itself.
DV/debug. accept_time and complete_time give the scoreboard a latency distribution for free, and an outstanding-age check at end of test (§13).
9. UVM — The Monitor
// ILLUSTRATIVE ONLY. A passive monitor. Two things matter more than the rest:
// it samples ACCEPTANCE, and it CLONES before publishing.
class ucie_in_monitor extends uvm_monitor;
`uvm_component_utils(ucie_in_monitor)
virtual ucie_if vif;
uvm_analysis_port #(ucie_txn) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
task run_phase(uvm_phase phase);
ucie_txn t;
forever begin
@(vif.mon_cb);
// ACCEPTANCE, not offering. A producer holds `valid` across a stall, so
// sampling `valid` alone emits one transaction per stalled cycle
// (25.4 §9) — the scoreboard then reports duplicates that never happened.
if (vif.mon_cb.valid && vif.mon_cb.ready) begin
t = ucie_txn::type_id::create("t");
t.sem_id = vif.mon_cb.sem_id;
t.generation = vif.mon_cb.generation;
t.op_class = vif.mon_cb.op_class;
t.traffic_class = vif.mon_cb.tc;
t.length = vif.mon_cb.length;
t.cfg_epoch = vif.mon_cb.cfg_epoch;
t.accept_time = $time;
// Publish a COPY. Subscribers may keep the handle for the rest of the
// test; reusing one object would let a later sample mutate a
// transaction the scoreboard is still holding.
ap.write(ucie_txn'(t.clone()));
end
end
endtask
endclassArchitecture. A passive observer that turns a boundary event into a model object. It drives nothing — a monitor that can affect the DUT is no longer measuring it.
State. None retained between samples. That is deliberate: a monitor holding state is a second model, and it will drift from the first.
Event. valid && ready in a clocking block, so sampling happens in the preponed region and does not race the design's non-blocking updates (21.6 §22).
Contract. Subscribers may retain the published handle indefinitely. Publishing the monitor's working object rather than a clone means a later sample mutates a transaction the scoreboard is still holding — which presents as a scoreboard mismatch on a transaction that was correct when it was observed.
Failure. Sampling valid alone is 25.4 §9's bug inside the testbench: one held offer becomes four transactions, and the scoreboard reports three duplicates the DUT never produced. The symptom points at the DUT and the bug is in the monitor.
DV/debug. If duplicates appear only under backpressure, check the monitor before the design — that correlation is the signature.
10. Object Lifetime and Cloning
| Situation | Rule |
|---|---|
| publishing from a monitor | clone — subscribers outlive the sample |
| storing in a scoreboard map | store the published handle; do not re-clone |
| passing to coverage | the handle is fine — coverage samples and discards |
| reusing one object across samples | never — the classic source of phantom mismatches |
And the debugging tell: a scoreboard mismatch where the expected transaction's fields look like a later transaction's is almost always a missing clone — the object was mutated after it was handed over.
11. UVM — The Scoreboard
// ILLUSTRATIVE ONLY. Not a two-line compare. This is where orphans, duplicates
// and stale generations are caught.
class ucie_scoreboard extends uvm_scoreboard;
`uvm_component_utils(ucie_scoreboard)
`uvm_analysis_imp_decl(_exp) // from the INDEPENDENT model (§7)
`uvm_analysis_imp_decl(_act) // from the output monitor
uvm_analysis_imp_exp #(ucie_txn, ucie_scoreboard) exp_imp;
uvm_analysis_imp_act #(ucie_txn, ucie_scoreboard) act_imp;
// Outstanding expectations, keyed by semantic identity. An associative array
// rather than a queue, because completions may legitimately arrive out of
// order (25.5 §12) and a FIFO compare would report false mismatches.
protected ucie_txn m_outstanding [bit [15:0]];
protected bit [7:0] m_live_gen [bit [15:0]];
protected int m_matched, m_orphan, m_dup, m_stale_gen;
function new(string name, uvm_component parent);
super.new(name, parent);
exp_imp = new("exp_imp", this);
act_imp = new("act_imp", this);
endfunction
// --- EXPECTED: the model says this operation should complete ---
function void write_exp(ucie_txn t);
if (m_outstanding.exists(t.sem_id)) begin
// The model expects an id that is already outstanding. Either the model
// is wrong, or the DUT reused an id while live (25.4 §12).
`uvm_error("SB_REALLOC",
$sformatf("sem_id 0x%0h expected again while still live (gen %0d vs %0d)",
t.sem_id, t.generation, m_live_gen[t.sem_id]))
end
m_outstanding[t.sem_id] = t;
m_live_gen[t.sem_id] = t.generation;
endfunction
// --- ACTUAL: the DUT produced a completion ---
function void write_act(ucie_txn t);
if (!m_outstanding.exists(t.sem_id)) begin
// ORPHAN: a completion for something never expected. Frequently a
// recovery that cleared semantic state (25.5 §18), or a stale straggler.
m_orphan++;
`uvm_error("SB_ORPHAN",
$sformatf("completion for sem_id 0x%0h gen %0d with no live expectation",
t.sem_id, t.generation))
return;
end
if (t.generation != m_live_gen[t.sem_id]) begin
// STALE GENERATION: right id, wrong use. This is the case that would
// silently retire the WRONG operation if the check were on id alone.
m_stale_gen++;
`uvm_error("SB_STALE_GEN",
$sformatf("sem_id 0x%0h completion gen %0d, live gen %0d",
t.sem_id, t.generation, m_live_gen[t.sem_id]))
return;
end
if (!m_outstanding[t.sem_id].compare(t)) begin
`uvm_error("SB_MISMATCH",
$sformatf("sem_id 0x%0h content mismatch", t.sem_id))
end else begin
m_matched++;
end
m_outstanding.delete(t.sem_id); // retire
endfunction
// --- END OF TEST: outstanding work is a FAILURE, not a warning ---
function void check_phase(uvm_phase phase);
if (m_outstanding.size() != 0) begin
foreach (m_outstanding[id])
`uvm_error("SB_INCOMPLETE",
$sformatf("sem_id 0x%0h gen %0d accepted at %0t never completed",
id, m_outstanding[id].generation, m_outstanding[id].accept_time))
end
if (m_matched == 0)
`uvm_error("SB_NOTHING_CHECKED", "scoreboard matched zero transactions")
endfunction
endclassArchitecture. An outstanding map keyed by semantic identity, with four distinct failure classes — realloc-while-live, orphan, stale generation, content mismatch — because they have four different root causes.
State. The outstanding map, the live generation per id, and four counters.
Event. write_exp on the model's prediction; write_act on an observed completion; retirement on a matched pair.
Contract. Keyed by identity, not order. Completions may legitimately arrive out of order (25.5 §12) — a queue-based compare would report false mismatches on a correct DUT, get "fixed" by relaxing the check, and then miss real bugs.
Failure. Checking on sem_id alone and omitting the generation check silently retires the wrong operation when an id is reused (25.4 §12) — the scoreboard reports a pass on a transaction that never completed.
DV/debug. check_phase is where most environments are weakest. Outstanding work at end of test is a failure, and the message names the id, generation and acceptance time so the trace can be located. And SB_NOTHING_CHECKED catches §7's mirrored predictor from the other direction — a scoreboard that matched zero transactions is not passing, it is idle.
12. UVM — Environment Topology
// ILLUSTRATIVE ONLY. Topology, with boilerplate trimmed. The connect_phase is
// where §7 is enforced or violated.
class ucie_env extends uvm_env;
`uvm_component_utils(ucie_env)
ucie_agent m_prot_agent; // ACTIVE
ucie_sb_agent m_cfg_agent; // ACTIVE
ucie_fault_agent m_fault_agent; // ACTIVE
ucie_in_monitor m_in_mon; // PASSIVE
ucie_out_monitor m_out_mon; // PASSIVE
ucie_link_monitor m_link_mon; // PASSIVE
ucie_semantic_model m_sem_model; // independent
ucie_transport_model m_xport_model;
ucie_scoreboard m_scb;
ucie_coverage m_cov;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_prot_agent = ucie_agent::type_id::create("m_prot_agent", this);
m_cfg_agent = ucie_sb_agent::type_id::create("m_cfg_agent", this);
m_fault_agent = ucie_fault_agent::type_id::create("m_fault_agent", this);
m_in_mon = ucie_in_monitor::type_id::create("m_in_mon", this);
m_out_mon = ucie_out_monitor::type_id::create("m_out_mon", this);
m_link_mon = ucie_link_monitor::type_id::create("m_link_mon", this);
m_sem_model = ucie_semantic_model::type_id::create("m_sem_model", this);
m_xport_model = ucie_transport_model::type_id::create("m_xport_model", this);
m_scb = ucie_scoreboard::type_id::create("m_scb", this);
m_cov = ucie_coverage::type_id::create("m_cov", this);
endfunction
function void connect_phase(uvm_phase phase);
// EXPECTED path: input observation -> independent model -> scoreboard.
m_in_mon.ap.connect(m_sem_model.analysis_export);
m_sem_model.exp_ap.connect(m_scb.exp_imp);
// ACTUAL path: output observation -> scoreboard. It goes NOWHERE ELSE.
m_out_mon.ap.connect(m_scb.act_imp);
// Transport observations inform the transport model and coverage, and are
// deliberately NOT connected into the semantic model (§7).
m_link_mon.ap.connect(m_xport_model.analysis_export);
m_link_mon.ap.connect(m_cov.analysis_export);
m_in_mon.ap.connect(m_cov.analysis_export);
endfunction
endclassArchitecture. Two disjoint paths into the scoreboard, and a transport model that observes but never feeds the semantic prediction.
State. Component handles only.
Event. Structural — the wiring is the design decision.
Contract. The output monitor's port connects to exactly one destination. A reviewer can check §7 by reading connect_phase alone: if m_out_mon.ap reaches anything other than m_scb.act_imp, the independence is broken.
Failure. Connecting m_out_mon.ap into m_sem_model is §7 — and it is a one-line change that looks like an improvement ("the model needs to know what happened").
DV/debug. Making the wiring reviewable is the point. A topology you can audit by reading one function is worth more than one that requires running a test to understand.
13. Wrong UVM — the Mirrored Predictor
// WRONG — the model is fed from the DUT's output, "so it stays in sync".
function void connect_phase(uvm_phase phase);
m_out_mon.ap.connect(m_sem_model.analysis_export); // <-- the bug
m_sem_model.exp_ap.connect(m_scb.exp_imp);
m_out_mon.ap.connect(m_scb.act_imp);
endfunction| Step | What happens |
|---|---|
| 1 | the DUT emits a completion — correct or not |
| 2 | the output monitor publishes it |
| 3 | the model receives it and predicts exactly that |
| 4 | the scoreboard receives the same object as expected and actual |
| 5 | they match, always |
| 6 | the regression is green from day one |
| 7 | a real bug ships |
Four properties.
There is no symptom. Coverage climbs, tests pass, and the number of matched transactions looks healthy — the environment is doing work and checking nothing.
It survives predictor code review, because the predictor may be perfectly correct. The defect is in connect_phase.
The subtler cousin is a model that calls a DUT function to compute an expectation (21.6 §24) — the model then inherits the design's reading of every ambiguous requirement.
And the detections are §12's reviewable wiring and §11's SB_NOTHING_CHECKED — plus a deliberate mutation test: inject a known bug and confirm the environment fails. An environment never proven to fail has never been proven to check (21.6 §25).
14. Sequence Completion Is Not Protocol Completion
item_done()means the driver has finished with the sequence item. It does not mean the peer received it, the transaction completed, a response returned, or a semantic obligation retired.
| Event | Means |
|---|---|
item_done() | the driver is free to take the next item |
| acceptance at the boundary | the DUT committed a resource (25.4 §8) |
| transmission | bits left; a retry may follow |
| completion | the semantic obligation is retired |
And conflating the first and the last is §15 — the most common way a UVM test passes while missing everything interesting.
15. Wrong UVM — the Objection Drops Too Early
// WRONG — the test's objection lifetime is tied to the sequence, and the
// sequence's lifetime is tied to item_done().
task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(env.m_prot_agent.m_seqr); // returns after the last item_done()
phase.drop_objection(this); // <-- too early
endtaskThe timeline:
| Cycle | Event |
|---|---|
| 1,000 | last sequence item driven; item_done() |
| 1,001 | seq.start() returns; objection dropped |
| 1,002 | UVM begins shutdown |
| 1,010 | the DUT is still processing the last few transactions |
| 1,040 | a completion arrives — but the run phase has ended |
| — | the scoreboard never sees it |
| — | check_phase reports outstanding work — if you wrote one |
| — | without check_phase, the test passes |
Four properties.
Whether this is caught depends entirely on §11's check_phase. With it, you get a clear SB_INCOMPLETE error naming the transaction. Without it, the test is green and the last N transactions were never checked.
It scales with pipeline depth, so a deeper design misses more — and the miss is silent.
The fix is a semantic drain condition, not a delay:
// CORRECTED. Hold the objection until the SEMANTIC work is retired, with a
// bounded timeout so a hang fails loudly instead of running forever.
task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(env.m_prot_agent.m_seqr);
fork
begin : drain
wait (env.m_scb.outstanding_count() == 0);
end
begin : guard
#(DRAIN_TIMEOUT_NS * 1ns);
`uvm_error("DRAIN_TIMEOUT",
$sformatf("%0d transactions still outstanding at drain timeout",
env.m_scb.outstanding_count()))
end
join_any
disable fork;
phase.drop_objection(this);
endtaskAnd the bounded guard matters as much as the wait. A bare wait on outstanding-zero hangs forever if the DUT drops a transaction — converting a clear failure into a timeout with no message. The guard turns it back into a named error with a count.
16. Recovery Must Be Tested Under Live Traffic
| Weak test | What it proves |
|---|---|
| idle → inject recovery → link returns to ACTIVE → pass | the link can retrain |
| traffic → recovery with work outstanding → verify completion | the semantic contract survives |
The strong sequence:
| Step | Check |
|---|---|
| 1 | drive traffic until N transactions are outstanding |
| 2 | inject a recovery event |
| 3 | snapshot the scoreboard's outstanding set |
| 4 | wait for recovery exit |
| 5 | assert the outstanding set is unchanged if the contract says retain |
| 6 | or assert replay occurs if it says replay |
| 7 | verify every outstanding transaction eventually completes |
| 8 | verify no orphan and no stale-generation error fired |
And step 8 is the one that catches 25.5 §18's failure — an adapter that cleared its correlation table on recovery produces orphan completions, and the scoreboard's SB_ORPHAN names it directly.
The reset controller in §5 exists for step 3. A controller that only pokes the DUT leaves the scoreboard's expectation stale — and then step 5 compares against a set that was already wrong.
17. Fault Injection
| Class | Tests |
|---|---|
| integrity fault | retry path (25.3 §10) |
| sustained backpressure | acceptance qualification (25.4 §8) |
| delayed completion | timeout ownership and id reuse (25.4 §12) |
| stale response | generation checking (§11) |
| recovery under load | §16 |
| configuration change under traffic | epoch discipline (25.6 §17) |
And the crossing that finds the interesting bugs is backpressure × retry (25.4 §18) — neither alone reproduces the acceptance-qualification bug, because it needs a long stall and a reason for the object to be re-offered.
No UCIe fault encoding is invented here (§4). These are architectural fault classes, injected at the testbench's own boundaries.
18. Assertions, and Where They Live
| Property | Lives | Why there |
|---|---|---|
| payload stable under stall | interface | it is a per-signal handshake rule |
| accepted exactly once | interface | one boundary, one cycle |
| no id reuse while live | interface or scoreboard | interface if the window is local |
| retry is not a new semantic accept | scoreboard | needs cross-layer correlation |
| completion only for a live generation | scoreboard | needs the outstanding map |
| config epoch stable for a live object | interface | local check, big payoff |
| recovery preserves obligations | scoreboard | it is a set comparison, not a signal |
| credit conservation | interface | two registers (25.6 §18) |
And the rule behind the table: an assertion belongs at the interface if it is about one boundary and a bounded window; it belongs in the scoreboard if it needs identity or history. A long-lived correlation written as an SVA property tends to become unmaintainable — the scoreboard is the right tool for lifetime.
19. Coverage That Means Something
// ILLUSTRATIVE ONLY. Crosses, not toggles. The individual coverpoints are
// nearly worthless alone; the crosses are the verification plan.
covergroup cg_ucie_semantic with function sample(ucie_txn t,
bit stalled,
bit retried,
bit in_recovery,
int outstanding);
cp_class : coverpoint t.traffic_class;
cp_stall : coverpoint stalled { bins no = {0}; bins yes = {1}; }
cp_retry : coverpoint retried { bins no = {0}; bins yes = {1}; }
cp_recovery : coverpoint in_recovery { bins no = {0}; bins yes = {1}; }
cp_epoch_chg : coverpoint t.cfg_epoch { bins same = {[0:0]}; bins changed = default; }
cp_outstanding: coverpoint outstanding { bins one = {1};
bins few = {[2:7]};
bins many = {[8:$]}; }
// THE POINT. A transaction that was stalled AND retried AND crossed a
// recovery, at high outstanding depth, is where the bugs in 25.4 and 25.5
// actually live — and no individual coverpoint asks for it.
x_stress: cross cp_class, cp_stall, cp_retry, cp_recovery, cp_outstanding;
endgroupArchitecture. Six coverpoints whose value is almost entirely in the cross.
State. The covergroup, sampled from the monitor stream.
Event. Sampled per accepted transaction, with the environment's current conditions passed in.
Contract. The sample() arguments come from the environment's own observation, not from the DUT's status registers — a coverage model that samples DUT state records what the DUT believes, which is not evidence.
Failure. Reporting the individual coverpoints as closure. cp_retry at 100% means retries happened somewhere; it does not mean a retry ever coincided with a stall at depth.
DV/debug. And the interview sentence: "Code coverage tells me which lines executed. It doesn't tell me whether a retry ever happened during a recovery with eight transactions outstanding — and that's where the bugs are."
20. The Verification Plan
| Requirement | Stimulus | Checker | Coverage | Failure evidence |
|---|---|---|---|---|
| accepted exactly once | backpressure sequences | interface SVA | stall-length bins | assertion + monitor count |
| exactly-once delivery | retry injection | scoreboard | retry × stall cross | SB_DUP |
| no orphan completion | recovery under load | scoreboard | recovery × outstanding | SB_ORPHAN |
| no stale-generation retire | delayed completion + id reuse | scoreboard | id-reuse-distance bins | SB_STALE_GEN |
| all work completes | any traffic | check_phase | outstanding-depth bins | SB_INCOMPLETE |
| config change is atomic | config agent under traffic | interface SVA | epoch-change cross | assertion |
| credit conservation | sustained load | interface SVA | credit-min bins | assertion + counters |
And the last column is what makes this a plan rather than a wish list. Every row names the artefact that will exist when it fails — which is what a reviewer should ask for.
21. Whiteboard Exercise
And a strong answer to item 8 is the orphan completion after recovery (25.5 §18): the transport checker sees a clean link, the semantic checker sees a completion for nothing, and only the correlation between them identifies that a transport event destroyed semantic state.
22. Weak Answers
| The answer | Why it is weak |
|---|---|
| "Sequencer, driver, monitor, scoreboard." | a component list, not an architecture — no boundaries, no independence |
| "The scoreboard compares expected to actual." | says nothing about where expected comes from (§7) |
"I'd monitor valid and build a transaction." | §9 — offering is not acceptance |
| "The test ends when the sequence ends." | §15 — the last transactions are never checked |
| "I'd test recovery by injecting it and checking the link comes back." | §16 — proves retraining, not the semantic contract |
| "We hit 100% code coverage." | §19 — which lines ran, not which conditions coincided |
| "One scoreboard for the whole stack." | 25.5 §17 — conflates failures with different owners |
23. Controlling the Next Question
| Close with | Invites | Which is |
|---|---|---|
| "…and the predictor has to be fed from the input side." | "what happens if it isn't?" | §13 — a complete story with no symptom |
"…item_done() isn't protocol completion." | "so when does the test end?" | §15 — drain plus bounded guard |
| "…recovery has to be tested with work outstanding." | "what would you check?" | §16's eight steps |
| "…the interesting coverage is the cross, not the coverpoints." | "which cross?" | §19 — retry × stall × recovery × depth |
And the first is the strongest hook in the chapter, because §13's failure has no symptom at all — which is a genuinely surprising thing to be able to describe.
24. Understanding Check
25. Summary
Five things.
Independence is the whole design (§7). The expected value must come from somewhere the DUT did not produce — and a mirrored predictor has no symptom.
Monitors sample acceptance and clone before publishing (§9, §10). Sampling valid alone puts 25.4 §9's bug inside the testbench, where it accuses the DUT.
The scoreboard keys on identity plus generation (§11), with four distinct failure classes and a check_phase that treats outstanding work as an error.
item_done() is not completion (§14, §15). Drain on semantic outstanding, with a bounded guard so a hang fails loudly.
And recovery is tested with work in flight (§16), because the interesting failure is an orphan completion that a clean link and a healthy transport checker cannot see.