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.

diagram
[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)
diagram
[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 all

check_phase and report_phase implementation

systemverilog
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)
endfunction
  • check_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

diagram
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 failed

Scenario B — act_imp not connected

diagram
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 bug

Scenario C — kind filter blocks all actuals

diagram
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 logic

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

  1. Confirm stimulus ran — driver activity on waves, sequencer items completed.

  2. Log monitor ap.write at UVM_HIGH — are transactions leaving the monitor?

  3. Log scoreboard write_exp and write_act at UVM_HIGH — do both fire?

  4. Print get_full_name() on scb, dut_agt.mon.ap, ref_model.ap in connect_phase.

  5. Verify act_imp wired to DUT monitor.ap — NOT driver, NOT sequencer.

  6. Verify exp_imp wired to ref_model.ap or golden monitor.ap — NOT dut_mon.

  7. Check exp_q.size() / exp_by_id.num() growth during run — expected consumed?

  8. Run single-transaction smoke test — expect exactly one match_count increment.

  9. Inspect kind filter — are actuals filtered out before compare?

  10. Check clone — are queued handles mutating? Print same pointer address.

  11. FIFO vs ID — false mismatches on OOO bus? Switch to ID map.

  12. check_phase drain — any leftover expected? report_phase summary sane?

systemverilog
// 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
diagram
[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 checked

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