Part 2 · Phases & Lifecycle · Intermediate
check_phase: End-of-Test Invariants and Error Drain
Using check_phase for aggregate pass/fail guards — mismatch ceilings, zero-transaction detection, coverage thresholds, and uvm_error discipline.
check_phase purpose
The check_phase callback evaluates end-of-test invariants and raises uvm_error or uvm_fatal when something is wrong. It is not for per-transaction comparison — that streams live in the scoreboard during run_phase. check_phase answers: did the test actually run, did we meet coverage goals, are aggregate counts sane?
[PHASE][UVM] check_phase decision flow
read extracted totals (from extract_phase)
│
├─ total_txns == 0 ──► uvm_error (premature end / no stimulus)
├─ mismatches > 0 ──► uvm_error (functional failure)
├─ cov < threshold ──► uvm_error (closure miss)
└─ all pass ──► no error (report_phase prints PASS)Traversal is bottom-up like extract — children check before parents.
Errors raised here increment the report server count used for PASS/FAIL.
A zero-transaction guard catches the classic 'objection never raised' bug.
Reference implementation
class my_env extends uvm_env;
int mismatches, exp_count, act_count;
real cov_pct;
real cov_goal = 90.0;
function void check_phase(uvm_phase phase);
super.check_phase(phase);
if (act_count == 0)
`uvm_error("CHECK", "no transactions observed — test did nothing")
if (mismatches > 0)
`uvm_error("CHECK", $sformatf("%0d scoreboard mismatches", mismatches))
if (exp_count != act_count)
`uvm_error("CHECK", $sformatf("count mismatch exp=%0d act=%0d",
exp_count, act_count))
if (cov_pct < cov_goal)
`uvm_error("CHECK", $sformatf("coverage %.1f%% below goal %.1f%%",
cov_pct, cov_goal))
endfunction
endclass// Test-level checks: feature-specific goals
class my_test extends uvm_test;
function void check_phase(uvm_phase phase);
super.check_phase(phase);
if (!cfg.soft_reset_exercised)
`uvm_error("CHECK", "soft_reset feature was not exercised")
endfunction
endclasscheck vs scoreboard boundary
Keep per-item expected-vs-actual compare in the scoreboard write() path. check_phase only sees aggregate counters. Mixing the two causes slow regressions and confusing failure messages.
Key takeaways
check_phase is for aggregate invariants, not streaming compare.
Zero-transaction and count-mismatch guards catch silent false passes.
Raise uvm_error with a consistent tag (CHECK) for log filtering.
Common pitfalls
Looping over thousands of transactions in check_phase.
Treating uvm_warning as pass — only ERROR and FATAL affect signoff.
Checking before extract — totals are stale if super.check_phase order is wrong.
Drain semantics
Errors raised in check_phase 'drain' into the report server. report_phase should consult get_severity_count(UVM_ERROR) rather than re-evaluating conditions. This single source of truth prevents PASS banners above failing checks.
[PHASE][UVM] error drain path
check_phase: uvm_error("CHECK", ...)
│
▼
uvm_report_server severity count += 1
│
▼
report_phase: errs = ERROR + FATAL counts → PASS or FAIL banner