Part 8 · Checking & Coverage · Intermediate
check_phase Drain, Summaries, and Debug
Drain unmatched expected, zero-compare sanity check, report_phase stats, and scoreboard debug checklist.
Why check_phase drain is mandatory
During run_phase, the scoreboard compares actuals against expected as they arrive. But what if the DUT never responds? Expected transactions sit in exp_q or exp_by_id forever, never consumed. Without a end-of-test drain, the test ends green — match_count may even be non-zero from partial traffic — while unanswered requests hide in the queue.
check_phase runs after run_phase drops all objections. No more stimulus, no more monitor samples. Anything still in the expected store is definitively unmatched — the DUT dropped a response, the ref model over-predicted, or connect_phase wired the wrong stream. Raising UVM_ERROR here converts silent passes into real failures.
[UVM] phase timeline — where drain fits
build_phase → create agents, scb, ref_model
connect_phase → wire monitor ports to scb imps
run_phase → stimulus + live compare (write_exp / write_act)
│
│ objections dropped — stimulus stopped
▼
check_phase → [CHECK] DRAIN — report unmatched expected, zero-compare check
▼
report_phase → summary statistics (match/mismatch/unexpected counts)[CHECK] drain scenarios — what leftovers mean
exp_q not empty (FIFO):
DUT never returned response for oldest outstanding request
OR act_imp not connected (actuals never arrived)
OR kind-filter dropped all actuals
exp_by_id not empty (ID map):
Specific IDs never got a response — per-ID UVM_ERROR possible
OR ref model predicted ID monitor doesn't propagate
match_count == 0:
No compare ever ran — broken connect, no stimulus, or filter blocks allcheck_phase and report_phase implementation
function void check_phase(uvm_phase phase);
super.check_phase(phase);
// FIFO drain
if (exp_q.size() != 0) begin
`uvm_error("SCB_DRAIN", $sformatf(
"%0d expected transactions never matched:", exp_q.size()))
foreach (exp_q[i])
`uvm_info("SCB_DRAIN", $sformatf(" [%0d] %s", i, exp_q[i].sprint()), UVM_NONE)
end
// ID map drain
if (exp_by_id.num() != 0) begin
`uvm_error("SCB_DRAIN", $sformatf(
"%0d IDs never got a response:", exp_by_id.num()))
foreach (exp_by_id[id])
`uvm_info("SCB_DRAIN", $sformatf(" id=%0h %s", id, exp_by_id[id].sprint()), UVM_NONE)
end
// Zero-compare sanity — test exercised nothing
if (match_count == 0 && mismatch_count == 0 && unexpected_count == 0)
`uvm_error("SCB", "zero compares — broken stimulus, monitor, or connect path")
endfunction
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("SCB_SUMMARY", $sformatf(
"match=%0d mismatch=%0d unexpected=%0d exp_q_left=%0d id_map_left=%0d",
match_count, mismatch_count, unexpected_count,
exp_q.size(), exp_by_id.num()), UVM_LOW)
endfunctioncheck_phase UVM_ERROR on any leftover — non-negotiable for self-checking.
Print each unmatched entry at UVM_NONE — visible even at low verbosity.
Zero-compare check catches wiring bugs that produce no traffic at scoreboard.
report_phase summary at UVM_LOW — one line per test in regression log.
Drain scenario walkthroughs
Scenario A — DUT drops one response
3 APB writes predicted → exp_q = [exp1, exp2, exp3]
DUT responds to writes 1 and 3 only — write 2 dropped (DUT bug)
After run_phase:
write_act × 2 → match_count = 2
exp_q = [exp2] ← still pending
check_phase:
UVM_ERROR: "1 expected transactions never matched"
print exp2 → addr=0x1004, data=0xBB → engineer knows which write failedScenario B — act_imp not connected
Ref model predicts 50 responses → exp_q has 50 entries
act_imp never connected in connect_phase
run_phase: write_exp × 50, write_act × 0
match_count = 0
check_phase:
UVM_ERROR: "50 expected transactions never matched"
UVM_ERROR: "zero compares"
→ immediate pointer to connect_phase bugScenario C — kind filter blocks all actuals
Monitor emits REQUEST and RESPONSE on same ap
write_act filters: if (t.kind != RESPONSE) return;
But monitor tags all txns as REQUEST (monitor bug)
write_exp queues 10 responses (from ref model)
write_act receives 10 actuals, filters ALL → compare never runs
check_phase: 10 unmatched + zero compares
→ check monitor kind assignment and filter logicDebug checklist — expanded triage guide
When the scoreboard misbehaves, work this checklist top to bottom. Most issues resolve at steps 1–4 without touching compare logic.
Confirm stimulus ran — driver activity on waves, sequencer items completed.
Log monitor ap.write at UVM_HIGH — are transactions leaving the monitor?
Log scoreboard write_exp and write_act at UVM_HIGH — do both fire?
Print get_full_name() on scb, dut_agt.mon.ap, ref_model.ap in connect_phase.
Verify act_imp wired to DUT monitor.ap — NOT driver, NOT sequencer.
Verify exp_imp wired to ref_model.ap or golden monitor.ap — NOT dut_mon.
Check exp_q.size() / exp_by_id.num() growth during run — expected consumed?
Run single-transaction smoke test — expect exactly one match_count increment.
Inspect kind filter — are actuals filtered out before compare?
Check clone — are queued handles mutating? Print same pointer address.
FIFO vs ID — false mismatches on OOO bus? Switch to ID map.
check_phase drain — any leftover expected? report_phase summary sane?
// Temporary debug verbosity — remove before regression freeze
function void write_act(bus_txn t);
`uvm_info("SCB_DBG", $sformatf("write_act: %s exp_q=%0d id_map=%0d",
t.sprint(), exp_q.size(), exp_by_id.num()), UVM_FULL)
// ... normal path ...
endfunction[CHECK] debug decision tree
zero compares?
├─ no write_act logs → act_imp not connected
├─ write_act but filtered → kind filter or monitor kind bug
└─ write_act fires, exp_q empty → ref model not predicting
false mismatches?
├─ shifted addr/data pattern → FIFO on OOO bus, use ID map
├─ random field wrong → clone bug (handle not copy)
└─ consistent one field → monitor sampling wrong phase
test passes but should fail?
└─ missing check_phase drain — leftovers not checkedKey takeaways
check_phase drain: UVM_ERROR on any leftover expected in queue or ID map.
Zero-compare check catches broken connect, stimulus, or filter.
report_phase summary gives one-line health per regression test.
Debug checklist: monitor logs → connect paths → filter → clone → FIFO vs ID.
Common pitfalls
Skipping check_phase drain — green test with pending expected.
UVM_FULL on every compare in regression — log explosion, use UVM_HIGH.
Only checking match_count > 0 — partial matches hide dropped responses.
Removing debug logs too early — keep connect_phase prints through bring-up.