Skip to content

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 checkingThe tell
decompositionyou name agents by boundary, not by block
active vs passive ownershipyou say which agents drive and which only observe
predictor independence§7 — the single biggest signal
semantic vs transport checkingtwo models, one correlation layer (§5)
do you know when a test may end?§14item_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

A block diagram of a UVM environment for a UCIe subsystem. A virtual sequencer drives three active agents: a protocol agent, a sideband and configuration agent, and a fault injection agent. All three drive the design under test. Passive monitors observe three boundaries: the protocol input boundary, the adapter boundary, and the link boundary. The protocol input monitor feeds an independent semantic reference model. The adapter and link monitors feed a transport model. Both models feed a cross-layer scoreboard, which also receives the output monitor observation. Coverage subscribers and a trace collector observe the monitor streams. A reset and recovery controller coordinates the agents.Virtual sequencercoordinates stimulusProtocol agentACTIVE — drivesSideband agentACTIVE — configFault agentACTIVE — injects (§17)DUTUCIe subsystemInput monitorPASSIVE — accepts (§9)Output monitorPASSIVE — observedSemantic modelINDEPENDENT (§7)Transport modelobjects + attemptsCross-layer SBcorrelates (§11)Coveragecrosses (§19)Reset controllerscope + drain (§16)12
A multi-agent environment for a UCIe subsystem. Stimulus flows down the left; every checking path flows from an independent input-side observation through a model to the scoreboard, never from the DUT output back into the prediction. The two models answer different questions — semantic and transport — and the correlation layer between them is where the bugs neither can see alone are caught.

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

AgentActive?DrivesWhy it exists
protocolactivesemantic requests at the protocol boundarythe primary stimulus
sideband / configactiveconfiguration, negotiation, mode changes§15 — config changes under traffic
fault injectionactiveintegrity faults, stalls, delayed completions§17
peer / BFMactive or passivethe far end, if the DUT is one side onlylets you test before the peer die exists
protocol-input monitorpassivefeeds the semantic model (§9)
adapter monitorpassiveobjects and attempts
link monitorpassivephysical 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ───────────────┘
                                        compare

Four 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endclass

Architecture. 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endclass

Architecture. 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

SituationRule
publishing from a monitorclone — subscribers outlive the sample
storing in a scoreboard mapstore the published handle; do not re-clone
passing to coveragethe handle is fine — coverage samples and discards
reusing one object across samplesnever — 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endclass

Architecture. 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endclass

Architecture. 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
StepWhat happens
1the DUT emits a completion — correct or not
2the output monitor publishes it
3the model receives it and predicts exactly that
4the scoreboard receives the same object as expected and actual
5they match, always
6the regression is green from day one
7a 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.

EventMeans
item_done()the driver is free to take the next item
acceptance at the boundarythe DUT committed a resource (25.4 §8)
transmissionbits left; a retry may follow
completionthe 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endtask

The timeline:

CycleEvent
1,000last sequence item driven; item_done()
1,001seq.start() returns; objection dropped
1,002UVM begins shutdown
1,010the DUT is still processing the last few transactions
1,040a 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
endtask

And 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 testWhat it proves
idle → inject recovery → link returns to ACTIVE → passthe link can retrain
traffic → recovery with work outstanding → verify completionthe semantic contract survives

The strong sequence:

StepCheck
1drive traffic until N transactions are outstanding
2inject a recovery event
3snapshot the scoreboard's outstanding set
4wait for recovery exit
5assert the outstanding set is unchanged if the contract says retain
6or assert replay occurs if it says replay
7verify every outstanding transaction eventually completes
8verify 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

ClassTests
integrity faultretry path (25.3 §10)
sustained backpressureacceptance qualification (25.4 §8)
delayed completiontimeout ownership and id reuse (25.4 §12)
stale responsegeneration checking (§11)
recovery under load§16
configuration change under trafficepoch 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

PropertyLivesWhy there
payload stable under stallinterfaceit is a per-signal handshake rule
accepted exactly onceinterfaceone boundary, one cycle
no id reuse while liveinterface or scoreboardinterface if the window is local
retry is not a new semantic acceptscoreboardneeds cross-layer correlation
completion only for a live generationscoreboardneeds the outstanding map
config epoch stable for a live objectinterfacelocal check, big payoff
recovery preserves obligationsscoreboardit is a set comparison, not a signal
credit conservationinterfacetwo 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
endgroup

Architecture. 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

RequirementStimulusCheckerCoverageFailure evidence
accepted exactly oncebackpressure sequencesinterface SVAstall-length binsassertion + monitor count
exactly-once deliveryretry injectionscoreboardretry × stall crossSB_DUP
no orphan completionrecovery under loadscoreboardrecovery × outstandingSB_ORPHAN
no stale-generation retiredelayed completion + id reusescoreboardid-reuse-distance binsSB_STALE_GEN
all work completesany trafficcheck_phaseoutstanding-depth binsSB_INCOMPLETE
config change is atomicconfig agent under trafficinterface SVAepoch-change crossassertion
credit conservationsustained loadinterface SVAcredit-min binsassertion + 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 answerWhy 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 withInvitesWhich 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.