Skip to content

UVM

Assertion Debug

Debugging a fired assertion — reading the failure report and reconstructing the failing attempt, the two-way audit (is the assertion a faithful statement of the spec, and did the DUT really violate it), distinguishing a real DUT bug from a wrong, too-strict, or unguarded assertion (reset, X, sampling), handling vacuous passes, and the systematic process from a fired assertion to its true cause.

Assertions in UVM · Module 21 · Page 21.4

The Engineering Problem

An assertion's great strength is precision: when it fires, it points at the exact cycle and names the property — so, unlike a scoreboard mismatch (which surfaces late and must be traced back), the location is given for free. But that strength hides a subtlety: the hard question is not "where" — it's "why" and "whose fault." A fired assertion is not automatically a DUT bug. It might be a real DUT bug (the design violated a correct rule), or a wrong assertion (too strict, wrong condition, wrong operator — Module 21.2), or an unguarded assertion firing on reset-state or X/unknown signals, or a sampling artifact. Assuming the fire means a DUT bug and debugging the RTL for days — when the assertion was wrong or unguardedwastes the effort on an innocent DUT, exactly as trusting a scoreboard's accusation does (Module 18.7). And the opposite failure mode — an assertion that passes vacuously (its antecedent never fired) — checks nothing while looking green. The problem this chapter solves is assertion debug: the systematic process from a fired assertion to its true causeauditing the assertion and the DUT — and diagnosing the vacuous-pass gap.

Assertion debug is finding the true cause of a fired assertion (or a vacuous pass). Because the fire is precise (the report gives the property, the failure cycle, the start cycle where the antecedent matched, and the signal values along the failing attempt), the task is not "where" but "why" and "whose fault" — a two-way audit. Audit the assertion against the spec: is it a faithful statement of the rule — the right window, condition, operator, and properly guarded (disable iff reset, X-handling)? If not, it's a wrong/too-strict/unguarded assertion (fix the assertion). Audit the DUT against the waveform: at the failing attempt, did the DUT really violate the rule — or is the fire a reset-state, X, or sampling artifact? Only if the assertion is correct and the DUT truly violated it (not an artifact) is it a real DUT bug (debug the RTL). The other debug scenario is the vacuous pass: an assertion passing without checking (antecedent never fired) — diagnosed via assertion coverage (a 0-hit antecedent), fixed by stimulus (steer to exercise it — Module 20.4), not by changing the assertion. This chapter is assertion debug: reading the report, reconstructing the attempt, the two-way audit, distinguishing real-bug from wrong-assertion, vacuity, and the systematic process.

How do you debug a fired assertion — read the failure report and reconstruct the failing attempt, audit the assertion against the spec and the DUT against the waveform, distinguish a real DUT bug from a wrong, too-strict, or unguarded assertion (reset, X, sampling), and handle the vacuous-pass case where an assertion checks nothing?

Motivation — why a precise fire still needs a careful audit

The precision of an assertion fire is seductive — it tempts you to jump to the DUT. The reasons a careful audit is essential:

  • A fire is an accusation, not a verdict. The assertion says the rule was broken — but the rule (the assertion) might be wrong, or the evidence (the signals) might be an artifact. Trusting the fire blindly and debugging the DUT risks days on an innocent design (Module 18.7's lesson, for assertions).
  • Assertions have their own failure modes. Too strict (wrong window — 21.2), wrong condition, wrong operator (|-> vs |=>), missing disable iff (fires during reset), X/unknown propagation, sampling/clocking races — any of these makes a correct DUT fire a wrong assertion. The fire doesn't distinguish them from a real bug.
  • The fire gives the location, so the audit can be focused. The report's precision (property, cycle, start, signals) is a gift — it lets the audit be targeted: you reconstruct the exact failing attempt and check it against the spec and the waveform, quickly.
  • Reset, X, and sampling are common and assertion-specific. A huge fraction of spurious fires are reset-state (missing disable iff), X values (uninitialized signals), or sampling (the assertion samples preponed values). Recognizing these is a core assertion-debug skill.
  • The vacuous pass is the silent failure. The opposite problem — an assertion that never fires its check (antecedent never exercised) — looks like success but verifies nothing. Diagnosing it (via assertion coverage) is part of assertion debug.

The motivation, in one line: an assertion fire is precise but not a verdict — it could be a real DUT bug or a wrong/too-strict/unguarded assertion (reset, X, sampling), so jumping to the DUT risks days on an innocent design; the precision lets the audit be focused (reconstruct the attempt, check assertion-vs-spec and DUT-vs-waveform), and the vacuous pass (an assertion that checks nothing) is the silent counterpart — so assertion debug is a careful, systematic audit, not a jump to the RTL.

Mental Model

Hold assertion debug as a sentry's alarm at a precise post and time — go to the scene, but audit whether the standing order was right and whether something real tripped it:

When a sentry's alarm goes off, you know exactly which post and what time — that's the assertion's precision, and it saves you the search. But the alarm itself is not proof of an intruder. Before you mobilize a manhunt (debug the DUT), you audit two things. First, the standing order: was the sentry's rule a faithful statement of the actual policy? A sentry told to sound the alarm if anyone passes within two seconds, when the policy allows three, will alarm on perfectly authorized people — a wrong order, not an intruder. Second, the evidence: did something real actually trip the alarm, or was it a false trigger — the alarm wired to go off during the nightly reset, or triggered by a sensor reading static (an X) before the system powered up, or by a mis-timed sample? Only if the standing order was correct and something real genuinely tripped it do you have an intruder (a real DUT bug). And there's a quieter failure: an alarm that never sounds because no one ever approached its post — the sentry verified nothing, not because all is well, but because nothing was ever tested there (a vacuous pass). Picture an assertion fire as a sentry's alarm at a precise post and time. The precision (the cycle and the property) saves you the search — you go straight to the scene. But the alarm is not proof of an intruder. Before you mobilize a manhunt (debug the DUT), you audit two things. First, the standing order: was the sentry's rule (the assertion) a faithful statement of the actual policy (the spec)? A sentry told to alarm if anyone passes within two seconds, when the policy allows three, alarms on authorized people — a wrong order (too-strict assertion), not an intruder. Second, the evidence: did something real actually trip the alarm, or was it a false trigger — the alarm wired to go off during the nightly reset (missing disable iff), triggered by a sensor reading static (an X) before power-up, or by a mis-timed sample? Only if the standing order was correct and something real genuinely tripped it do you have an intruder (a real DUT bug). And there's a quieter failure: an alarm that never sounds because no one ever approached its post — the sentry verified nothing, not because all is well, but because nothing was ever tested there (a vacuous pass).

So assertion debug is auditing a precise alarm: the fire gives the scene (cycle + property — no search), but you audit the standing order (is the assertion faithful to the spec — right window/condition/operator/guards?) and the evidence (did the DUT really violate, or is it a reset/X/sampling false trigger?). Only a correct order truly tripped is an intruder (real DUT bug). And the quiet failure is the alarm that never sounds (vacuous pass — antecedent never approached). Go to the scene the fire gives you, but audit the order and the evidence before convicting the DUT — and watch for the alarm that never checked anything.

Visual Explanation — what a fired assertion could mean

The defining picture is the fork: a fire is one of a real DUT bug, a wrong assertion, or an artifact — and the audit determines which.

A fired assertion is one of: real DUT bug, wrong assertion, or artifactcorrect assertion, truly violatedcorrectassertion, trul…or wrong assertionor anartifacttoo strict / wrong condition / wrong operatortoo strict / wrongcondition / wrong…reset / X / samplingAssertion firedprecise cycle + propertyReal DUT bugdesign violated the ruleWrong assertionfires on legal behaviorArtifactfalse triggerToo strict / wrongcond/opfix the assertionReset / X / samplingguard the assertion12
Figure 1 — a fired assertion has several possible causes, and the audit determines which. The fire could be a real DUT bug: the design violated a correct, well-guarded assertion. Or a wrong assertion: too strict, wrong condition, or wrong operator, firing on legal behavior. Or an artifact: the assertion fired during reset (missing disable iff), on X/unknown values, or from a sampling/clocking issue. The fire itself doesn't distinguish them. Auditing the assertion against the spec and the DUT against the waveform determines the true cause — only a correct, guarded assertion truly violated is a real DUT bug.

The figure shows the fork a fired assertion presents. The fire (the warning-colored center — precise cycle + property) could be a real DUT bug (the design violated a correct, well-guarded assertion), or a wrong assertion (too strict, wrong condition, or wrong operatorfiring on legal behavior), or an artifact (reset — missing disable iff; X/unknown values; sampling/clocking). The crucial reading is that the fire itself doesn't distinguish theseall present as "assertion X fired at cycle T." So the debugging task is determining which, by auditing: the wrong-assertion branch is found by checking the assertion against the spec (is the window/condition/operator correct?); the artifact branch is found by checking the failing cycle's context (was it reset? are signals X? a sampling race?); and only when both audits clear (the assertion is correct and guarded, the violation is real) is it a real DUT bug. The brand-colored real-DUT-bug is the conclusion only after the success-colored wrong-assertion and artifact branches are ruled out. This mirrors scoreboard debug (Module 18.7): don't convict the DUT until you've audited the accuser (the assertion) and the evidence (the waveform). The insidious trap is following only the real-DUT-bug branch — assuming the fire is a design bug and debugging the RTL — when the wrong-assertion or artifact branches are more often the cause (especially early in a project, when assertions are new and un-vetted). The diagram is the cause map: one fire → three possible causes (real bug / wrong assertion / artifact) → audit to determine — and the audit, not the fire, gives the verdict. A fire is one of real-bug, wrong-assertion, or artifact — audit to determine which, don't assume a DUT bug.

RTL / Simulation Perspective — reading the report and auditing

In code (and in the report), the debug is reconstruct the failing attempt, then audit the assertion and the DUT. The example shows a report, the reconstruction, and the audit checks.

reading an assertion failure report and auditing the cause
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === THE FAILURE REPORT (from the simulator) ===
//   "a_handshake" FAILED at time 1850ns (cycle 185)
//      property: req |-> ##[1:3] ack
//      antecedent matched (started) at cycle 182; consequent NOT satisfied by cycle 185
//      signals: req=1@182, ack=0 @183,184,185, rst_n=1, all defined
 
// === RECONSTRUCT THE FAILING ATTEMPT on the waveform ===
//   cycle 182: req rose (antecedent) → attempt starts
//   cycles 183,184,185: ack never asserted → window [1:3] elapsed → FIRE at 185
 
// === AUDIT 1: is the ASSERTION faithful to the spec? ===
//   spec says "ack within 1 to 3 cycles" → ##[1:3] is CORRECT (not too strict/loose)
//   operator |-> correct? spec means ack AFTER req → arguably |=> ... but [1:3] covers it → OK
//   guarded? disable iff (!rst_n) present, rst_n=1 at failure → NOT a reset artifact
//   signals defined (no X) → NOT an X artifact
 
// === AUDIT 2: did the DUT REALLY violate it? ===
//   waveform: ack genuinely never asserted in 183..185 → REAL violation (not a sampling artifact)
//   → ASSERTION correct + guarded, DUT truly violated → REAL DUT BUG → debug the RTL
 
// ✗ CONTRAST — an UNGUARDED assertion firing at reset (the DebugLab):
//   a_x: assert property (req |-> ##[1:3] ack);   // ← NO disable iff (!rst_n)
//   fires at cycle 2 during reset, req/ack are reset-state/X → ARTIFACT, not a DUT bug

The code shows the debug flow. The failure report gives the assertion name (a_handshake), the time/cycle of failure (1850ns, cycle 185), the property (req |-> ##[1:3] ack), the antecedent start (cycle 182), and the signals along the attempt (req=1@182, ack=0@183,184,185, rst_n=1, all defined). Reconstruct the failing attempt: cycle 182 req rose (attempt starts), cycles 183–185 ack never asserted, window [1:3] elapsed → fire at 185. Audit 1 (assertion vs spec): the spec says "ack within 1 to 3 cycles"##[1:3] is correct (not too strict/loose); the operator is reasonable; it's guarded (disable iff (!rst_n) present, rst_n=1 at failure → not a reset artifact); signals defined (no X). Audit 2 (DUT vs waveform): ack genuinely never asserted in 183–185 → a real violation (not a sampling artifact). Conclusion: assertion correct and guarded + DUT truly violatedreal DUT bugdebug the RTL. The contrast (commented) is an unguarded assertion (a_x with no disable iff) firing at cycle 2 during reset on reset-state/X signals → an artifact, not a DUT bug (the DebugLab). The shape to carry: assertion debug is (1) reconstruct the failing attempt from the report (antecedent cycle, window, what happened), (2) audit the assertion against the spec (window, condition, operator, guards), and (3) audit the DUT against the waveform (real violation vs reset/X/sampling artifact) — concluding a real DUT bug only when both audits clear. The report's precision makes step (1) fast (the cycles and signals are given); the audits (steps 2–3) are where the true cause is found. The guards check (disable iff, X) is assertion-specific — a huge fraction of spurious fires are reset or X, and checking rst_n and signal-definedness at the failure cycle immediately rules them in or out. Reconstruct the attempt, audit the assertion's correctness and guards, audit the DUT's real behavior — conclude a DUT bug only when both clear.

Verification Perspective — the two-way audit

The core of assertion debug is the two-way auditthe assertion (vs the spec) and the DUT (vs the waveform). Seeing both axes, with what each rules in or out, is the diagnostic.

Two-way audit: assertion vs spec, DUT vs waveform; both clear means real DUT bugaudit theassertionaudit the DUTwrong window/cond/op/guard → fix assertionwrongwindow/cond/op/guar…reset/X/sampling→ artifactassertion correct + guardedassertioncorrect +…violation genuineAssertion firedaudit bothAudit vs specwindow, condition, operator,guardsAudit vs waveformreal violation?reset/X/sampling?Wrong/unguardedfix the assertionArtifactguard / fix samplingReal DUT bugboth audits clear12
Figure 2 — the two-way audit of a fired assertion. Audit the assertion against the spec: is the window, condition, and operator faithful, and is it guarded against reset and X? If not, it's a wrong or unguarded assertion — fix the assertion. Audit the DUT against the waveform at the failing attempt: did the consequent genuinely not hold, with defined signals out of reset, or is it a sampling artifact? If the violation isn't real, it's an artifact. Only when the assertion is correct and guarded AND the DUT genuinely violated it is the fire a real DUT bug. Both audits must clear.

The figure shows the two-way audit. Audit the assertion against the spec (the brand-colored top): is the window, condition, and operator faithful, and is it guarded against reset and X? If not, it's a wrong or unguarded assertion → fix the assertion. Audit the DUT against the waveform at the failing attempt (the brand-colored bottom): did the consequent genuinely not hold, with defined signals out of reset — or is it a sampling artifact? If the violation isn't real, it's an artifactguard / fix sampling. Only when the assertion is correct and guarded AND the DUT genuinely violated it is the fire a real DUT bug (the success-colored conclusion — both audits clear, the bus edges). The verification insight is that the two audits are independent and both necessary: the assertion could be wrong (a correct DUT fires a bad assertion), or the evidence could be an artifact (a correct DUT fires a good assertion spuriously, e.g. during reset), or both could clear (a real bug). Skipping the assertion audit risks debugging the DUT for a wrong-assertion fire; skipping the DUT audit risks "fixing" a real bug as an artifact (or vice versa). The assertion audit checks fidelity (Module 21.2 — the right window/condition/operator) and guards (disable iff reset, X-handling — assertion-specific). The DUT audit checks the waveformdid the consequent really not hold, with defined signals out of reset, not a sampling race? The brand-colored audits gate the success-colored real-DUT-bug conclusion. This is the operational form of "audit the accuser and the evidence before convicting the accused" (Module 18.7, for assertions): both the assertion (accuser) and the waveform evidence must clear before the DUT (accused) is convicted. The figure is the audit: fire → (audit assertion: correct+guarded?) + (audit DUT: real violation?) → both clear → real DUT bugtwo independent audits, both required. Audit the assertion's correctness and guards and the DUT's real behavior; only when both clear is it a DUT bug.

Runtime / Execution Flow — the systematic process

At debug time, assertion debug is a fixed process: read the report, reconstruct the attempt, audit the assertion, audit the DUT, conclude. The flow shows it.

Assertion debug: read report, reconstruct, audit assertion, audit DUT, concluderead the report → reconstruct the failing attempt → audit the assertion (spec, guards) → audit the DUT (waveform, reset/X/sampling) → conclude (wrong assertion / artifact / real DUT bug)read the report → reconstruct the failing attempt → audit the assertion (spec, guards) → audit the DUT (waveform, reset/X/sampling) → conclude (wrong assertion / artifact / real DUT bug)1Read the failure reportthe property, the failure cycle, the antecedent start cycle, andthe signals along the failing attempt.2Reconstruct the failing attempton the waveform: where the antecedent matched, the window, and whatthe consequent actually did.3Audit the assertion and the DUTassertion vs spec (window, condition, operator, guards); DUT vswaveform (genuine violation vs reset/X/sampling).4Conclude the true causewrong/unguarded assertion (fix it), artifact (guard it), or — bothaudits clear — a real DUT bug (debug the RTL).
Figure 3 — the systematic assertion-debug process. Read the failure report: the property, the failure cycle, the antecedent start cycle, the signals. Reconstruct the failing attempt on the waveform: where the antecedent matched, the window, and what the consequent did. Audit the assertion against the spec: faithful window, condition, operator, and guards. Audit the DUT against the waveform: a genuine violation versus a reset, X, or sampling artifact. Conclude: wrong or unguarded assertion (fix it), artifact (guard it), or — only when both audits clear — a real DUT bug (debug the RTL).

The flow shows the systematic process. Read (step 1): the failure report — the property, the failure cycle, the antecedent start cycle, and the signals along the failing attempt. Reconstruct (step 2): the failing attempt on the waveformwhere the antecedent matched, the window, and what the consequent actually did. Audit (step 3): the assertion vs the spec (window, condition, operator, guards) and the DUT vs the waveform (genuine violation vs reset/X/sampling). Conclude (step 4): wrong/unguarded assertion (fix it), artifact (guard it), or — both audits clear — a real DUT bug (debug the RTL). The runtime insight is that this process exploits the fire's precision: steps 1–2 are fast (the report gives the cycles and signals, so reconstructing the attempt is directno tracing back, unlike a scoreboard mismatch), and the effort goes into steps 3–4 (the audits and the conclusion). The process guarantees you never debug the DUT until the assertion and the evidence are cleared — the cardinal discipline. The assertion audit (step 3) often short-circuits the debug: if the assertion is too strict (wrong window) or unguarded (fires during reset), the fire is explained without the DUT — saving the days a jump-to-RTL would waste. The DUT audit (step 3) catches the artifactsreset, X, sampling — that look like violations but aren't. Only when both audits clear does step 4 reach "real DUT bug." The process is the debugging loop for an assertion: read (precise report) → reconstruct (the attempt) → audit (assertion + DUT) → conclude (wrong-assertion / artifact / real-bug) — a deterministic path from a fired assertion to its true cause, exploiting the precision while auditing the accusation. Read the report, reconstruct the attempt, audit both the assertion and the DUT, and conclude — never jump to the RTL before the audits clear.

Waveform Perspective — reading the failing attempt

The debug happens on the waveform: you reconstruct the failing attempt — antecedent, window, consequent — and read whether the violation is real. The waveform shows a real violation and a vacuous (no-check) attempt.

Reading a failing attempt: antecedent, window, and a genuinely-absent consequent — versus a vacuous attempt that checked nothing

12 cycles
Reading a failing attempt: antecedent, window, and a genuinely-absent consequent — versus a vacuous attempt that checked nothingreq rises (antecedent), rst_n=1 (out of reset), signals defined → real attempt startsreq rises (antecedent)…window [1:3] elapsed, ack never asserted → FIRE — a genuine violation (not reset/X)window [1:3] elapsed, …req never rises here → no attempt → assertion passes VACUOUSLY (checks nothing)req never rises here →…reading the waveform: the fire was real; the later region checked nothingreading the waveform: …clkrst_nreqackfirevacuoust0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — reading the failing attempt on the waveform. The assertion is req implies ack within 1 to 3 cycles, with rst_n high (out of reset) and signals defined (no X). In the first attempt, req rises (antecedent) and the window opens; ack genuinely never asserts within the window, so the assertion fires — and the waveform confirms it's a real violation, not a reset or X artifact (rst_n=1, signals defined). Later, req never rises, so no attempt starts and the assertion passes vacuously, checking nothing in that region. Reading the waveform tells you whether a fire is real and whether a pass actually checked.

The waveform shows reading the failing attempt. The assertion is req |-> ##[1:3] ack, with rst_n high (out of reset) and signals defined (no X). In the first attempt, req rises (antecedent) and the window opens; ack genuinely never asserts within the window, so the assertion fires (cycle 4) — and the waveform confirms it's a real violation, not a reset or X artifact (rst_n=1, signals defined). Later, req never rises, so no attempt starts and the assertion passes vacuously, checking nothing in that region. The crucial reading is the two diagnoses the waveform supports. First, was the fire real? — at the failing attempt, you check: did the antecedent match (req rose)? was it out of reset (rst_n=1)? were the signals defined (no X)? did the consequent genuinely not hold (ack truly absent in the window)? All yes → a real violation (the assertion correctly caught a DUT bug). If any no (reset, X, antecedent-didn't-really-match) → an artifact. Second, did a pass actually check? — in the vacuous region, req never rose, so the assertion passed without checking — a pass that verifies nothing (the vacuous-pass diagnosis). The picture to carry is that the waveform is the audit surface: you reconstruct the failing attempt and read the signals at the failure cycle to distinguish a real violation (antecedent matched, out of reset, defined, consequent truly absent) from an artifact (reset/X/sampling), and you read whether a passing region actually checked (antecedent fired) or passed vacuously. Reading the waveform this way — at the failing attempt, is the violation genuine and the assertion properly conditioned? and elsewhere, did the assertion actually check? — is the heart of assertion debug. The genuine fire (out of reset, defined, consequent absent) versus the vacuous region (no antecedent) is the signature of reading an assertion's behavior: real violation here, no check there. The waveform is where you audit the fire — confirm the antecedent matched, the signals were valid, and the consequent truly failed, before calling it a DUT bug.

DebugLab — three days debugging a DUT that never had the bug

An assertion firing during reset, mistaken for a DUT bug and chased through the RTL

Symptom

A newly-added assertion a_resp_valid fired in the regressionconsistently, at the very start of every test, cycle 2 or 3. The team, seeing a firing assertion, assumed a real DUT bug and opened the RTL: they traced the response-valid logic, the reset sequencing, the state machine — for three days, across several engineers — finding nothing wrong. The DUT was behaving exactly as specified. Eventually, someone looked at the failing cycle on the waveform and noticed: the assertion was firing at cycle 2, while rst_n was still lowduring reset. The signals it was checking were in their reset/undefined state, not meaningful. The assertion had no disable iff (!rst_n) guard, so it was evaluating during reset and firing on garbage. The DUT never had the bug; the assertion was unguarded.

Root cause

The assertion was missing its reset guard (disable iff (!rst_n)), so it evaluated during reset on reset-state/undefined signals and fired spuriously; the team assumed the fire meant a DUT bug and debugged the RTL for days without first auditing the assertion:

why three days were lost debugging an innocent DUT
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ ASSUME the fire is a DUT bug → debug the RTL (no assertion audit):
  a_resp_valid: assert property (@(posedge clk) start |-> ##[1:2] resp_valid);  // ← NO disable iff!
  // fires at cycle 2 DURING RESET → signals are reset-state/X → spurious fire
  // team sees "assertion fired" → assumes DUT bug → traces RTL for 3 DAYS → finds nothing (DUT is fine)
 
✓ AUDIT the assertion FIRST — check the failing cycle's context:
  // look at the failure cycle: rst_n=0 (in reset!) → the fire is a RESET ARTIFACT, not a DUT bug
  // FIX: guard the assertion against reset
  a_resp_valid: assert property (@(posedge clk) disable iff (!rst_n) start |-> ##[1:2] resp_valid);
  // now the assertion doesn't evaluate during reset → no spurious fire → real violations still caught
  // a 30-SECOND audit (check rst_n at the failure cycle) would have saved 3 DAYS

This is the unaudited-assertion-fire bug — the cardinal assertion-debug failure, and the direct parallel to scoreboard debug (Module 18.7). The assertion a_resp_valid was missing its disable iff (!rst_n) guard, so it evaluated during reset — when rst_n is low and the signals are in their reset/undefined state — and fired on garbage at cycle 2. The team, seeing a firing assertion, assumed it meant a real DUT bug and debugged the RTL for three days across several engineersfinding nothing, because the DUT was correct. The whole effort was wasted on an innocent design, because the team trusted the fire and skipped the assertion audit. A 30-second audit — looking at rst_n at the failure cycle and seeing it was 0 (in reset) — would have immediately revealed the fire as a reset artifact, not a DUT bug. The fix is trivial once diagnosed: guard the assertion with disable iff (!rst_n) so it doesn't evaluate during reset — and real violations (out of reset) are still caught. The root error is the same as trusting a scoreboard's accusation (18.7): a firing assertion is as likely a wrong or unguarded assertion as a real DUT bug, so jumping to the DUT without auditing the assertion risks days on an innocent design. The general lesson, and the chapter's thesis: audit the assertion before debugging the DUT — a firing assertion is as likely a wrong, too-strict, or unguarded assertion (firing during reset, on X, from a sampling race, or from a too-strict/wrong property) as a real DUT bug, so check the failing cycle's context (is it reset? are signals X? is the property faithful to the spec?) and audit the assertion's correctness and guards before concluding a DUT bug; only when the assertion is correct and properly guarded AND the DUT genuinely violated it is it a real bugauditing the accuser (the assertion) before convicting the accused (the DUT) saves the days otherwise lost debugging an innocent design. A firing assertion points precisely at a cycle — but check whether that cycle is reset, whether the signals are valid, and whether the rule is right, before you spend three days in the RTL.

Diagnosis

The tell is an assertion firing at start-of-test or consistently at the same early cycle. Diagnose an unaudited fire:

  1. Check rst_n at the failure cycle. A fire while rst_n is low is a reset artifact from a missing disable iff, not a DUT bug.
  2. Check for X/unknown signals at the failure cycle. An assertion evaluating on uninitialized or X values fires spuriously; the signals must be defined.
  3. Audit the property against the spec. Before the RTL, confirm the window, condition, and operator are faithful — a too-strict or wrong assertion fires on legal behavior.
  4. Only then audit the DUT. With the assertion cleared as correct and guarded, check the waveform for a genuine violation before debugging the RTL.
Prevention

Audit the assertion before the DUT:

  1. Always guard assertions against reset. Use disable iff (!rst_n) (or a default disable) so assertions don't evaluate during reset.
  2. Handle X/unknown values. Ensure the property doesn't fire on uninitialized signals; check definedness in the condition where needed.
  3. Audit the property at every fire. Make the first debug step checking the assertion's correctness and guards, not opening the RTL.
  4. Treat a fire as an accusation, not a verdict. A firing assertion is as likely a wrong assertion as a DUT bug; clear the assertion before convicting the design.

The one-sentence lesson: audit the assertion before debugging the DUT — a firing assertion is as likely a wrong, too-strict, or unguarded assertion (firing during reset, on X, or from a too-strict property) as a real DUT bug, so check the failing cycle's context (reset, X) and the property's fidelity to the spec before concluding a DUT bug, because a 30-second assertion audit saves the days otherwise lost debugging an innocent design.

Common Mistakes

  • Assuming a fire is a DUT bug. A firing assertion is as likely a wrong or unguarded assertion; audit the assertion before opening the RTL.
  • Not checking reset and X at the failure cycle. A fire during reset (missing disable iff) or on X values is an artifact, not a violation; check rst_n and definedness first.
  • Skipping the property audit. A too-strict, wrong-condition, or wrong-operator assertion fires on legal behavior; confirm fidelity to the spec.
  • Ignoring vacuous passes. An assertion whose antecedent never fires passes without checking; diagnose via assertion coverage and steer stimulus, not by changing the assertion.
  • Not reconstructing the failing attempt. Debugging without the antecedent cycle, window, and signals guesses; the report gives them — reconstruct the attempt precisely.
  • Fixing a real bug as an artifact (or vice versa). Both audits must clear; don't guard away a real violation or chase an artifact into the RTL.

Senior Design Review Notes

Interview Insights

You find the true cause through a systematic process: read the report, reconstruct the failing attempt, audit the assertion against the spec and the DUT against the waveform, and conclude — and crucially, you don't assume the fire is a DUT bug. An assertion's strength is precision: when it fires, the report gives you the property, the failure cycle, the antecedent start cycle, and the signals along the failing attempt, so unlike a scoreboard mismatch you don't have to search for where — the location is given. That makes the task not where but why and whose fault. The first step is to reconstruct the failing attempt on the waveform: where the antecedent matched, the evaluation window, and what the consequent actually did. Then comes the two-way audit. Audit the assertion against the spec: is the window, condition, and operator faithful to the rule, and is it properly guarded against reset and X? If not, it's a wrong, too-strict, or unguarded assertion, and you fix the assertion. Audit the DUT against the waveform at the failing attempt: was rst_n high so you're out of reset, were the signals defined with no X, and did the consequent genuinely not hold — or is the fire a reset, X, or sampling artifact? Only when the assertion is correct and properly guarded and the DUT genuinely violated it is the fire a real DUT bug, at which point you debug the RTL. The cardinal discipline mirrors scoreboard debug: a firing assertion is as likely a wrong or unguarded assertion as a real DUT bug, so you audit the accuser — the assertion — before convicting the accused — the DUT. The most common trap is assuming the fire means a DUT bug and debugging the RTL for days, when a thirty-second check of rst_n at the failure cycle would have shown the assertion fired during reset because it lacked a disable iff guard. So the process exploits the precision for fast reconstruction but always audits the assertion and the evidence before concluding a real design bug.

Besides a real DUT bug, a fire can be a wrong assertion or an artifact, and the fire itself doesn't distinguish them. A wrong assertion is one that doesn't faithfully capture the spec, so it fires on legal behavior. This includes too strict — a window narrower than the spec, like checking for an ack within two cycles when the spec allows three, so a legal ack at cycle three fires it. It includes a wrong condition — the antecedent or a qualifier is wrong, so the assertion checks in the wrong context. And it includes the wrong implication operator — using overlapping where non-overlapping was meant or vice versa, an off-by-one cycle that fires falsely. The fix for all of these is to correct the assertion to match the spec. An artifact is a spurious fire from the assertion evaluating in conditions where it shouldn't. The most common is a reset artifact: the assertion lacks a disable iff reset guard, so it evaluates during reset when signals are in their reset state or undefined, and fires on garbage. Another is an X artifact: the assertion evaluates on uninitialized or X-valued signals before the system is properly powered up, producing a spurious fire. And there are sampling or clocking artifacts: concurrent assertions sample preponed values, so a race or a mis-aligned clock can make the assertion see a value that looks like a violation when the real behavior was fine. The fix for artifacts is to guard the assertion — add disable iff reset, handle X, ensure correct clocking. The reason this matters is that especially early in a project, when assertions are newly written and un-vetted, wrong assertions and artifacts are often more common than real DUT bugs. So when an assertion fires, you can't assume it's a design bug — you audit the assertion's correctness and guards and the failing cycle's context first. Only when the assertion is confirmed faithful and properly guarded, and the violation is genuine and out of reset on defined signals, is it a real DUT bug. The fire is an accusation that could come from a wrong accuser or false evidence, not automatically a verdict against the DUT.

You tell them apart with the two-way audit: audit the assertion against the spec to check if it's faithful and guarded, and audit the DUT against the waveform to check if the violation is genuine — a real DUT bug is when both clear, while a wrong assertion is when the spec audit fails. Start with the assertion audit, because it's often faster and is the step people skip. Take the property and compare it to the spec rule it's meant to capture. Is the window exactly the spec's window, not narrower or wider? Is the condition — the antecedent and any qualifiers — correct? Is the implication operator right for same-cycle versus next-cycle? And is it guarded — does it have disable iff reset so it won't fire during reset, and does it handle X? If any of these is wrong, the assertion is the problem: it's too strict, wrong, or unguarded, and it fired on behavior that's actually legal or in a context where it shouldn't evaluate. You fix the assertion, not the DUT. If the assertion audit clears — the property faithfully states the spec and is properly guarded — then audit the DUT against the waveform at the failing attempt. Confirm rst_n was high so you're genuinely out of reset, the signals were defined with no X, the antecedent really matched, and the consequent genuinely did not hold within the window. If all that holds, the DUT really did violate a correct rule — a real DUT bug — and you debug the RTL. The key insight is that you can't tell from the fire alone; you have to do both audits. A correct DUT can fire a wrong assertion, and a correct DUT can fire a correct assertion spuriously during reset, and a buggy DUT can fire a correct assertion genuinely — all three look identical in the failure report. Only auditing the assertion's fidelity and guards, and the waveform's actual behavior, separates them. The discipline is to audit the assertion first because it's quick and catches the common wrong-assertion and reset-artifact cases, saving you from debugging an innocent DUT, and to convict the design only when both the accuser and the evidence check out.

A vacuous pass is when an assertion passes not because the checked behavior was correct but because its antecedent never fired, so it never actually evaluated its rule — and in debugging, it's the silent failure: an assertion that looks green but verified nothing on the paths where its antecedent didn't occur. Unlike a fire, which is loud and points at a cycle, a vacuous pass is quiet — the assertion reports passing, the regression is green, but the assertion did no real work. This is dangerous because it gives false confidence: you think a rule is verified, but the triggering condition never arose in your tests, so the rule was never actually exercised. The way you detect it is assertion coverage — specifically, covering the antecedent. You add a cover property for the assertion's antecedent, and then you check whether it was hit. If the antecedent cover shows zero hits, the assertion passed vacuously throughout — it never checked anything — and that's a coverage gap, not a passing result. So in debugging, when you're assessing whether an assertion suite actually verified the protocol, you don't just look at whether assertions passed; you look at whether their antecedents fired. An assertion with a never-hit antecedent is effectively absent. The fix is on the stimulus side, not the assertion side. The antecedent never firing means the scenario that triggers the rule was never generated, so you steer the constrained-random stimulus to exercise it — bias constraints or add a directed test to drive the antecedent — exactly like closing any coverage gap. This is important to distinguish from the fire case: a vacuous pass is not something wrong with the assertion or the DUT; the assertion is fine and the DUT may be fine, but you simply never tested the path. So you don't change the assertion — you change the stimulus to make the assertion actually check. Handling vacuous passes is part of assertion debug because verifying that your assertions did real work is as important as debugging the ones that fired; a suite full of vacuously-passing assertions provides false assurance, and antecedent coverage is what reveals it.

An assertion needs a reset guard — disable iff reset — so it doesn't evaluate during reset, because during reset the signals are in their reset state or undefined, and evaluating the property against them produces spurious fires that aren't real violations. When a design is in reset, its signals aren't in their normal operational state — they may be held at reset values, or be undefined or X early in simulation before reset properly initializes them. A protocol assertion like request implies ack within a window assumes the signals carry their normal meaning, which they don't during reset. So if the assertion evaluates during reset, it can see a request-like value that isn't a real request, or expect an ack that legitimately isn't there because the design isn't operating yet, and it fires — a spurious fire on garbage, not a real violation. Without the guard, this typically shows up as an assertion firing at the very start of every test, at cycle two or three, consistently. The disable iff reset clause tells the simulator to not evaluate the assertion — to abort any in-progress attempts and not start new ones — while the reset condition holds, so the assertion only checks when the design is actually operating. What happens without it is the classic assertion-debug trap: the assertion fires during reset, an engineer sees a firing assertion and assumes a real DUT bug, and debugs the RTL for days finding nothing, because the DUT is fine — the assertion was just evaluating during reset on meaningless signals. A thirty-second check of rst_n at the failure cycle would reveal it's firing in reset, pointing immediately at the missing guard rather than a design bug. So the reset guard is both a correctness requirement — assertions should only check operational behavior — and a major source of false fires when omitted. Best practice is to apply it broadly, often via a default disable iff at the top of a checker module so every assertion inherits it, and to always check the reset context at the failure cycle when debugging a fire. Beyond reset, similar care is needed for X values — an assertion evaluating on uninitialized signals fires spuriously too — so guarding against both reset and X is part of writing robust assertions that fire only on real violations.

Exercises

  1. Reconstruct the attempt. Given a report (property, failure cycle 185, antecedent at 182, signals), describe the failing attempt and the first audit you'd run.
  2. Classify the fire. For a fire at cycle 2 with rst_n low, a fire on a legal max-latency response, and a fire with a genuinely-absent consequent out of reset, name the cause of each.
  3. Add the guard. Write the reset guard for an assertion firing at start-of-test, and explain why it fixes the spurious fire.
  4. Diagnose the vacuous pass. An assertion passes but its antecedent cover is 0. Explain what that means and the fix.

Summary

  • Assertion debug is finding the true cause of a fired assertion — and because the fire is precise (the report gives the property, failure cycle, antecedent start, and signals), the task is not "where" but "why" and "whose fault."
  • It is a two-way audit: audit the assertion against the spec (faithful window, condition, operator, and guarded against reset and X) and audit the DUT against the waveform (a genuine violation vs a reset, X, or sampling artifact) — only when both clear is it a real DUT bug.
  • The cardinal rule mirrors scoreboard debug (Module 18.7): a firing assertion is as likely a wrong or unguarded assertion as a real DUT bug, so audit the accuser (the assertion) before convicting the accused (the DUT)jumping to the RTL wastes days on an innocent design.
  • The vacuous pass is the silent case: an assertion passing without checking (antecedent never fired) — diagnosed via a 0-hit antecedent cover, fixed by stimulus (steer to exercise it), not by changing the assertion.
  • The durable rule of thumb: an assertion fire is a precise accusation, not a verdict — reconstruct the failing attempt from the report, then audit the assertion (is the window/condition/operator faithful, and is it guarded against reset and X?) and the DUT (a genuine violation, or a reset/X/sampling artifact?), and conclude a real DUT bug only when both clear; check rst_n and definedness at the failure cycle first, because a 30-second assertion audit saves the days otherwise lost debugging an innocent design — and watch for vacuous passes that check nothing.

Next — Assertion Coverage Synergy: the module closes by uniting assertions and coverage — the synergy between assertion-based checking and functional coverage. How cover properties measure that assertions actually fired their checks, how assertion coverage feeds the closure picture, how vacuous passes connect to coverage gaps, and how assertions and coverage together close the loop on both correctness and completeness of the protocol checking.