Skip to content

UVM

check_phase

The second cleanup phase — judging gathered results: asserting end-of-test conditions, run-time vs end-of-test checking, and why message severity (error/fatal) decides pass/fail.

UVM Phasing · Module 5 · Page 5.8

The Engineering Problem

extract_phase gathered the run's final results; check_phase is where they're judged. It is the second cleanup phase, and its job is to decide pass/fail: evaluate the gathered end-of-test state and assert the conditions that must hold for the test to be considered successful — no transactions left outstanding, queues drained, counts reconciling, coverage goals met.

There are two subtleties that make check_phase worth its own chapter. First, it is a different kind of checking from what happens during the run. The scoreboard checks per-transaction correctness continuously, during run_phase; check_phase checks end-of-test conditions — the final, whole-run properties that can only be judged once everything is over. Both kinds of checking are needed, and they live in different places. Second — and this is where real bugs hide — detecting a problem is not the same as failing the test. UVM's pass/fail verdict is driven by the error/fatal message count, so a check that notices a failure but reports it at the wrong severity (uvm_info or uvm_warning) detects the problem and still passes. This chapter is about the judgement step: what end-of-test conditions to check, how it differs from run-time checking, and why severity is what turns a detected problem into a failed test.

What is check_phase, what end-of-test conditions does it judge, how does it differ from the per-transaction checking done during the run, and why does the severity of a check's message decide whether a detected problem actually fails the test?

Motivation — why a dedicated judgement phase, and why severity matters

check_phase being the judgement step, with severity-driven verdicts, is what makes a pass meaningful:

  • End-of-test conditions can only be judged at the end. "No transactions outstanding," "the expected queue is empty," "coverage goals met," "counts reconcile" — these are whole-run properties, true only once the run is over and the results gathered. They have no meaningful value mid-run, so they're judged in check_phase, after extract_phase collected them.
  • It complements run-time checking, doesn't replace it. Per-transaction correctness (was this response right?) is checked continuously during the run by the scoreboard; completeness and final state (did everything happen, is nothing left over?) are checked at the end by check_phase. A robust test needs both — and they live in different phases for a reason.
  • Severity is the verdict mechanism. UVM decides pass/fail from the count of UVM_ERROR/UVM_FATAL messages. So how a check reports a failing condition determines whether the test fails: uvm_error fails it, uvm_info/uvm_warning do not. A check that detects a real problem but logs it at the wrong severity is a silent failure — the problem was seen and the test still passed.
  • It decides the verdict before it's announced. check_phase settles pass/fail; report_phase then communicates it. Separating the decision from the announcement keeps each clean — the verdict is determined by the checks, and the report reflects it.

The motivation, in one line: a meaningful pass requires judging the end-of-test conditions (not just per-transaction correctness), and check_phase is where that judgement happens — but only checks reported at error/fatal severity actually fail the test, so severity is the difference between detecting a problem and catching it.

Mental Model

Hold check_phase as the final audit that signs off — or flags failures that actually count:

check_phase is the closing audit, and only its formally-filed findings change the verdict. Throughout the game, the referee made continuous calls (run-time per-transaction checking — was each play legal?). Now, at the end, the auditor reviews the whole game's final figures (gathered by extract_phase) and judges the end-state conditions: did the books balance, is nothing left outstanding, were all the required boxes ticked? This is a different review from the in-game calls — it's about the final, complete picture. But here's the catch: the auditor's findings only change the official result if they're filed as formal failures. A finding scribbled as a casual note (uvm_info/uvm_warning) is seen but doesn't count — the result still stands as a pass. Only a finding filed as a formal failure (uvm_error/uvm_fatal) actually overturns the verdict. So the audit must both find the problem and file it at the right severity for it to matter.

So in a check_phase, you judge the gathered end-of-test conditions — and you report any failing condition with uvm_error (or uvm_fatal), because that's what makes it count. Detecting a problem and logging it as info is the trap: the audit saw it, but the verdict didn't change.

Visual Explanation — check in the cleanup sequence

check_phase is the second cleanup phase — between extract_phase (gather) and report_phase (announce) — and it's where the verdict is decided.

check_phase judges the gathered results, deciding pass/fail, between extract and reportextract (gather) → check (judge) → report (announce)extract (gather) → check (judge) → report (announce)1extract_phase — gatheredthe final results and residual state are collected (previouschapter).2check_phase — judgeassert end-of-test conditions; failures via uvm_error/uvm_fataldecide pass/fail.3report_phase — announcecommunicate the verdict and summary based on the checks.4final_phase — clean uplast teardown after the verdict is reported.
Figure 1 — check_phase, the judgement step, in the cleanup sequence. extract_phase gathered the final results; check_phase judges them — asserting end-of-test conditions (no outstanding transactions, queues drained, counts reconcile, coverage met) and deciding pass/fail via error/fatal severity; report_phase then announces the verdict; final_phase cleans up. check is where the result is decided, before it's reported.

check_phase is the decision point of the cleanup. By the time it runs, extract_phase has gathered the final, settled figures — the matched/mismatched counts, the leftover queue sizes, the coverage numbers — so check_phase has complete data to judge. Its job is to assert the end-of-test conditions: it evaluates each property that must hold for success and, for any that fails, issues a failure message. The pass/fail of the test is the result of these checks: if check_phase (and any run-time checking) issued no errors, the test passes; if it issued errors, the test fails. Then report_phase communicates that verdict. The clean separation — extract gathered, check judges, report announces — means check_phase is purely about judgement: it doesn't gather (already done) or announce (next), it decides. And the mechanism by which it decides is severity, which the next sections make precise.

RTL / Simulation Perspective — asserting end-of-test conditions

A check_phase evaluates the gathered results and issues failures (at error severity) for any condition that doesn't hold. Below, a scoreboard checks the end-of-test conditions on the values extract_phase gathered.

check_phase — judge gathered results; severity decides the verdict
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class my_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(my_scoreboard)
  int num_matched, num_mismatched;          // accumulated during run
  int unsigned num_outstanding;             // gathered in extract_phase
 
  function void check_phase(uvm_phase phase);
    super.check_phase(phase);
 
    // COMPLETENESS: nothing left unmatched at end-of-test
    if (num_outstanding != 0)
      `uvm_error("SB", $sformatf("%0d transactions left unmatched at end-of-test", num_outstanding))
 
    // CORRECTNESS tally: any mismatches accumulated during the run
    if (num_mismatched != 0)
      `uvm_error("SB", $sformatf("%0d mismatches detected", num_mismatched))
 
    // SANITY: the test actually exercised something
    if (num_matched == 0)
      `uvm_error("SB", "no transactions were checked — the run did nothing")
  endfunction
endclass

Every check here uses `uvm_error — deliberately, because that's what makes a failing condition fail the test. The phase judges three end-of-test conditions on the gathered data: completeness (num_outstanding != 0 means responses never arrived — incomplete run), the correctness tally (num_mismatched != 0 means transactions disagreed during the run), and a sanity check (num_matched == 0 means nothing was exercised — a likely hollow pass). Each is an end-of-test property — judged once, on the final figures, not per-transaction. The critical detail is the severity: had any of these used `uvm_info or `uvm_warning instead of `uvm_error, the condition would be detected and logged but the test would still pass, because UVM's verdict counts errors and fatals, not infos and warnings. So check_phase both finds the failing conditions and reports them at error severity — and the second part is what turns detection into a failed test.

Verification Perspective — run-time checking vs end-of-test checking

UVM has two kinds of checking in two places: continuous per-transaction checking during the run, and end-of-test condition checking in check_phase. Knowing the difference is knowing what to check where.

Run-time per-transaction checking in the scoreboard during run_phase, versus end-of-test condition checking in check_phase after the runduring therunafter the runrun_phase (continuous)as transactions flowRun-time checkingper-transaction correctness: was THISresponse right?check_phase (once)after the run endsEnd-of-test checkingwhole-run conditions: nothing outstanding,counts reconcile, coverage met12
Figure 2 — two kinds of checking, two places. Run-time checking happens continuously during run_phase, in the scoreboard: it judges each transaction's correctness as it's observed (was this response right?). End-of-test checking happens once in check_phase, after the run: it judges whole-run, final conditions (is nothing outstanding, do counts reconcile, are coverage goals met?). Both are needed — per-transaction correctness during the run, completeness and final state at the end. They are complementary, not redundant.

The two kinds of checking answer different questions. Run-time checking — done by the scoreboard during run_phase, as each transaction is observed — answers "was this transaction correct?": it compares each observed response against the expected value and flags a mismatch immediately. It's continuous and per-transaction, and it's how correctness is verified throughout the run. End-of-test checking — done in check_phase, after the run — answers "is the whole run in a valid final state?": no transactions left outstanding, queues drained, counts reconciled, coverage goals met. It's a one-time judgement of completeness and final conditions, on the figures extract_phase gathered. Both are essential and neither replaces the other: a run can be perfectly correct in every transaction it checked (run-time) yet incomplete or in a bad final state (caught only by end-of-test checking), and vice versa. check_phase is specifically the home of the end-of-test kind — the whole-run, final-state checks that the per-transaction run-time checking structurally can't make.

Runtime / Execution Flow — severity decides the verdict

The pass/fail verdict is not "did a check notice a problem?" — it's "how many UVM_ERROR/UVM_FATAL messages were issued?" So the severity a check uses is what determines whether a detected problem actually fails the test.

A detected failing condition reported with uvm_error fails the test; reported with uvm_info/warning the test still passeserror/fatalinfo/warningcheck_phasedetects afailingconditionReported atwhatseverity?uvm_error /uvm_fatal → errorcount increasesuvm_info /uvm_warning → errorcount unchangedTest FAILS(errors > 0)Test PASSES(despite thedetectedproblem!)
Figure 3 — severity decides the verdict. A check detects a failing condition. If it reports with uvm_error or uvm_fatal, the error count increases and the test FAILS. If it reports the same condition with uvm_info or uvm_warning, the condition is logged but the error count is unchanged, so the test PASSES despite the detected problem. UVM's pass/fail is driven by the error/fatal count — detection alone doesn't fail the test; the right severity does.

This is the most important — and most error-prone — fact about check_phase: the verdict is the error count, not whether a check "noticed" something. UVM tallies UVM_ERROR and UVM_FATAL messages, and the test passes if and only if that tally is zero (and no fatals occurred). So a check_phase that detects a failing condition — finds the leftover queue non-empty, finds a count that doesn't reconcile — only fails the test if it reports that detection with uvm_error (or uvm_fatal). Report the very same condition with uvm_info or uvm_warning, and the problem is logged but the error count doesn't move, so the test passes — a check that found the bug and let it through. This decouples detection from verdict, and the bridge between them is severity. The discipline is absolute: a failing condition must be reported at error/fatal severity, or the check is hollow. uvm_info/uvm_warning are for information, never for a condition that should fail the test.

Waveform Perspective — judging after the run

On a timeline, check_phase is a zero-time cleanup sliver after extract_phase, where the gathered final values are judged and the verdict is decided — before report_phase announces it.

check_phase judges the gathered results at zero time, after extract

10 cycles
check_phase judges the gathered results at zero time, after extractrun_phase: per-transaction (run-time) checking happens continuouslyrun_phase: per-transac…extract (EXT) gathered the final figures; check (CHK) now judges themextract (EXT) gathered…check_phase asserts end-of-test conditions (uvm_error fails the test)check_phase asserts en…clkphaseRUNRUNRUNRUNRUNEXTCHKCHKRPTRPTvaliddata00A0A100B0B100000000t0t1t2t3t4t5t6t7t8t9
Figure 4 — during run_phase, transactions flow and run-time per-transaction checking happens continuously (valid/data active). After the run, the zero-time cleanup phases run: extract (EXT) gathered the final figures, then check (CHK) judges the end-of-test conditions — deciding pass/fail via error severity — then report (RPT) announces. The phase track shows check after extract; it is the judgement step, where end-of-test conditions are asserted on the settled values, with no pin activity.

The phase track shows CHK after EXT, both zero-time after the run. During the RUN span, run-time checking is happening continuously (per-transaction, in the scoreboard) — the correctness checking. Then the cleanup phases run: EXT gathered the figures, and CHK judges them — asserting the end-of-test conditions on the settled values. No pins move during CHK because it's zero-time judgement, not behaviour. The two checking kinds are visible in their two places: the continuous run-time checking spread across the RUN region, and the one-time end-of-test checking in the CHK sliver. And it's in that CHK sliver that the verdict is decided — by the severity of the messages the checks issue, which is what the test's pass/fail ultimately counts.

DebugLab — the check that found the bug and passed anyway

A check_phase that detected a failure but reported it as info — so the test passed

Symptom

A scoreboard's check_phase correctly detected that transactions had been left unmatched at end-of-test — the log even printed a clear message saying so ("12 transactions left unmatched"). Yet the test reported PASS. The check ran, found the problem, logged it... and the test passed anyway. The bug was visible in the log but invisible to the verdict.

Root cause

The failing condition was reported at the wrong severityuvm_info instead of uvm_error — so it didn't increment the error count that drives the verdict:

why the detected failure didn't fail the test
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
check_phase:   if (num_outstanding != 0)
                 `uvm_info("SB", $sformatf("%0d unmatched", num_outstanding), UVM_LOW)   // ✗ info!
UVM verdict:   pass/fail = count of UVM_ERROR / UVM_FATAL messages
the message:   UVM_INFO → does NOT increment the error count
result:        problem detected and LOGGED, but error count = 0 → test PASSES
fix:           `uvm_error("SB", $sformatf("%0d transactions left unmatched", num_outstanding))

The check's logic was right — it correctly identified the unmatched transactions — but its severity was wrong. UVM determines pass/fail solely from the count of UVM_ERROR and UVM_FATAL messages; an UVM_INFO (or UVM_WARNING) message, however alarming its text, does not affect the verdict. So the condition was detected and printed but never counted, and the test passed with the failure sitting in plain sight in the log. Detection without error-severity reporting is a hollow check.

Diagnosis

The tell is a failure visible in the log but a passing verdict — a severity mismatch. Diagnose verdict bugs at the report severity:

  1. Check the severity of failing conditions. Any condition that should fail the test must be reported with uvm_error or uvm_fatal. If a check uses uvm_info/uvm_warning for a real failure, the test passes despite it. Grep the checks for the message severity.
  2. Reconcile the log against the verdict. If the log shows a problem ("N unmatched", "mismatch") but the test passed, the problem was reported at non-error severity. The verdict counts errors/fatals, not log alarm.
  3. Confirm the error count drives the result. Pass requires zero errors and no fatals. A check that "noticed" a problem without raising an error doesn't change that count — and so doesn't change the verdict.
Prevention

Report failing conditions at the severity that fails the test:

  1. Use uvm_error/uvm_fatal for any condition that should fail. Reserve uvm_info for information and uvm_warning for non-failing concerns. A condition that must fail the test gets error or fatal severity — that's what the verdict counts.
  2. Treat detection and verdict as separate, bridged by severity. Detecting a problem isn't enough; it must be reported at error severity to count. Make "does this condition fail the test?" → "then report it as an error" an automatic step.
  3. Verify a known failure actually fails. Inject a condition that should fail (e.g., force a leftover transaction) and confirm the test fails, not just logs. A check you've never seen fail the test is unproven — it may be reporting at the wrong severity.

The one-sentence lesson: UVM's pass/fail is the count of error/fatal messages, so a check_phase that detects a failure but reports it with uvm_info/uvm_warning passes despite the bug — report failing conditions with uvm_error/uvm_fatal, because detection only counts at the right severity.

Common Mistakes

  • Reporting a failing condition at non-error severity. UVM's verdict counts UVM_ERROR/UVM_FATAL; a real failure logged with uvm_info/uvm_warning is detected but doesn't fail the test. Use error/fatal for anything that should fail.
  • Relying only on run-time checking. Per-transaction correctness (during the run) doesn't catch end-of-test conditions (completeness, final state). check_phase must judge the whole-run conditions too.
  • Checking before the data is gathered. check_phase judges what extract_phase gathered; doing the gathering inside check, or checking values not yet extracted, mixes the concerns. Gather in extract, judge in check.
  • Not checking completeness. A pass needs both correctness and completeness — assert no outstanding transactions, queues drained, counts reconcile, coverage met. Omitting these lets incomplete runs pass.
  • Putting the verdict announcement in check. Deciding the verdict (via error severity) is check's job; printing the summary is report_phase. Keep judgement and announcement separate.
  • A check you've never seen fail. An unproven check may report at the wrong severity or never trigger. Fault-inject to confirm a known-bad condition actually fails the test.

Senior Design Review Notes

Interview Insights

check_phase is the second cleanup phase (after extract_phase), and its job is to judge the gathered results and decide pass/fail. It asserts the end-of-test conditions — the whole-run, final properties that must hold for the test to be successful: no transactions left outstanding, the expected queue drained, counts reconciling, coverage goals met. It runs after extract_phase has gathered the final, settled figures, so it has complete data to judge, and it's a zero-time, bottom-up function phase like the other cleanup phases. The key thing to understand is that check_phase is a different kind of checking from what happens during the run: the scoreboard checks per-transaction correctness continuously during run_phase (was this response right?), while check_phase checks end-of-test conditions once, after the run (is the whole run in a valid final state?). Both are needed. And critically, the way check_phase actually fails the test is by issuing uvm_error or uvm_fatal messages — UVM's pass/fail verdict is the count of those, so a check that detects a problem but reports it at a lower severity (info/warning) detects it and still passes. So check_phase both judges the end-of-test conditions and must report failures at error severity for them to count.

Exercises

  1. Write the checks. Write a check_phase for a scoreboard that asserts no transactions are outstanding, there were zero mismatches, and at least one transaction was checked — at the correct severity for each.
  2. Fix the verdict. A check_phase logs "10 unmatched transactions" but the test passes. State the cause and the one-word change (severity) that fixes it.
  3. Two kinds of checking. For each, name whether it's run-time or check_phase checking: (a) comparing a response to its expected value as it arrives; (b) asserting the expected queue is empty at end-of-test; (c) flagging a coverage goal not met; (d) catching a single wrong data byte mid-run.
  4. Prove it fails. Describe how you'd confirm a check_phase actually fails the test on a known-bad condition, and why a check you've never seen fail is unproven.

Summary

  • check_phase is the second cleanup phase — zero-time, bottom-up — where the gathered results are judged and the pass/fail verdict is decided. It asserts end-of-test conditions: no transactions outstanding, queues drained, counts reconcile, coverage goals met.
  • It is a different kind of checking from run-time checking: the scoreboard checks per-transaction correctness continuously during the run; check_phase checks whole-run, end-of-test conditions once, after the run. Both are needed — correctness during the run, completeness at the end.
  • The verdict is the error/fatal count: a failing condition reported with uvm_error/uvm_fatal fails the test; the same condition reported with uvm_info/uvm_warning is detected and still passes. Severity is the bridge between detection and verdict.
  • The classic bug is a check that finds the failure but reports it at the wrong severity — logged in plain sight, but the test passes. Report failing conditions at error/fatal severity, and fault-inject to confirm a known-bad condition actually fails the test.
  • The durable rule of thumb: judge the end-of-test conditions in check_phase (completeness and final state, not just per-transaction correctness), and report every failing condition with uvm_error/uvm_fatal — because UVM's verdict counts errors, so a check only catches what it reports at the right severity.

Next — report_phase: the verdict is decided; now it's announced. report_phase is the third cleanup phase — where each component reports its results and the final PASS/FAIL summary is communicated, based on the checks just made. The next chapter is the announcement step.