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, afterextract_phasecollected 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_FATALmessages. So how a check reports a failing condition determines whether the test fails:uvm_errorfails it,uvm_info/uvm_warningdo 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_phasesettles pass/fail;report_phasethen 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_phaseis 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 byextract_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 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.
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
endclassEvery 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.
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.
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 cyclesThe 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
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.
The failing condition was reported at the wrong severity — uvm_info instead of uvm_error — so it didn't increment the error count that drives the verdict:
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.
The tell is a failure visible in the log but a passing verdict — a severity mismatch. Diagnose verdict bugs at the report severity:
- Check the severity of failing conditions. Any condition that should fail the test must be reported with
uvm_errororuvm_fatal. If a check usesuvm_info/uvm_warningfor a real failure, the test passes despite it. Grep the checks for the message severity. - 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.
- 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.
Report failing conditions at the severity that fails the test:
- Use
uvm_error/uvm_fatalfor any condition that should fail. Reserveuvm_infofor information anduvm_warningfor non-failing concerns. A condition that must fail the test gets error or fatal severity — that's what the verdict counts. - 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.
- 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 withuvm_info/uvm_warningis 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_phasemust judge the whole-run conditions too. - Checking before the data is gathered.
check_phasejudges whatextract_phasegathered; 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
- Write the checks. Write a
check_phasefor 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. - Fix the verdict. A
check_phaselogs "10 unmatched transactions" but the test passes. State the cause and the one-word change (severity) that fixes it. - 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.
- Prove it fails. Describe how you'd confirm a
check_phaseactually fails the test on a known-bad condition, and why a check you've never seen fail is unproven.
Summary
check_phaseis 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_phasechecks 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_fatalfails the test; the same condition reported withuvm_info/uvm_warningis 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 withuvm_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.