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?

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

systemverilog
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
systemverilog
// 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
endclass

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

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