UVM
Assertion-Based Verification
Assertion-based verification as a methodology — assertions as executable specification, who writes them (design's internal invariants versus verification's external protocol), dynamic simulation versus formal property checking, the trade-off against transaction-level scoreboard checking, and how ABV layers into the constrained-random, coverage-driven flow as one part of a complete checking strategy.
Assertions in UVM · Module 21 · Page 21.3
The Engineering Problem
The prior chapters integrated assertions (21.1) and wrote them (21.2) — the mechanics. This chapter steps back to the methodology: what is assertion-based verification as an approach, and where does it fit? The questions are strategic, not syntactic. Who should write an assertion — the design engineer who knows the internal micro-architecture, or the verification engineer who knows the external protocol? How is an assertion checked — only in simulation (on the stimulus you happen to run), or formally proven for all inputs? When is an assertion the right tool versus the transaction-level scoreboard — and what happens when you force the wrong one (a complex data computation into an assertion, or a protocol timing rule into a scoreboard)? And the most dangerous misconception: a team with a thorough, all-passing assertion suite in simulation assumes the protocol is proven — when the assertions only checked the paths the stimulus exercised, leaving a latent violation in an un-driven path to ship. The problem this chapter solves is ABV as a methodology: assertions as executable specification, who writes them, dynamic versus formal, the trade-off with scoreboard checking, and ABV's place in the complete strategy.
Assertion-based verification (ABV) is the methodology of capturing the design's intended behavior as assertions — executable specification — and using them throughout the flow. An assertion is the spec rule written formally: unambiguous (a precise property, not ambiguous English), checkable (verified automatically every run), self-documenting (the property is the rule), and design-resident (it lives with the RTL). Who writes them depends on what they check: design engineers write white-box assertions on internal invariants (a FIFO never overflows, an FSM is one-hot) — caught early, at the source; verification engineers write black-box assertions on external protocol/spec behavior. How they're checked spans two engines: dynamic (simulation — checked on the stimulus you run, scenario-bound) and formal (property checking — a tool proves the property for all inputs or finds a counterexample, exhaustive). ABV trades off with the scoreboard: assertions for local, temporal, protocol/invariant rules (precise, early, formal-amenable); the scoreboard for end-to-end data computation (a reference model). And ABV layers into the constrained-random, coverage-driven flow — assertions check protocol/invariant rules continuously, the scoreboard checks function, coverage measures thoroughness — as one part of a complete checking strategy. This chapter is ABV the methodology: executable spec, who writes, dynamic vs formal, the scoreboard trade-off, and the layered strategy.
What is assertion-based verification as a methodology — how are assertions executable specification, who writes them (design's internal invariants versus verification's external protocol), how do dynamic simulation and formal property checking differ, when are assertions the right tool versus the scoreboard, and how does ABV layer into the complete coverage-driven flow?
Motivation — why capture the spec as assertions
A spec in English is intent; assertions turn that intent into something enforceable. The reasons ABV is a methodology, not just a technique:
- English specs are ambiguous and un-checkable. A prose rule ("the bus must not be granted to two masters") is open to interpretation and checked only by human review. An assertion is the same rule written formally — unambiguous and checked automatically, every run.
- Assertions are executable specification. The property is the rule — so it documents the exact intended behavior (self-documenting) and enforces it (checkable). The spec becomes executable, bridging intent and implementation.
- The right author knows the rule. A design engineer knows the internal invariants (the micro-architecture's assumptions); a verification engineer knows the external protocol (the spec's interface contract). Who writes an assertion follows who knows what it checks — and both contribute layered coverage.
- Dynamic and formal are two engines for the same assertions. The same property can be simulated (checked on your stimulus) and formally proven (exhaustive, all inputs). ABV spans both — and understanding the difference prevents the fatal assumption that passing in simulation means proven.
- Assertions and the scoreboard are complementary tools. Assertions excel at local, temporal rules; the scoreboard excels at end-to-end data. Choosing the right tool (and not forcing the wrong one) is part of the methodology.
The motivation, in one line: a spec in English is ambiguous intent, but assertions make it executable specification — unambiguous, checkable, self-documenting, design-resident — written by whoever knows the rule (design's internal invariants, DV's external protocol), checked by two engines (dynamic simulation, formal proof), and complementary to the scoreboard — so ABV is a methodology for capturing and enforcing the design's intent across the whole flow.
Mental Model
Hold ABV as turning the spec's contract clauses into enforceable, machine-checkable form, embedded with the design — enforced by spot-checking the runs (dynamic) or proving the perimeter (formal):
A specification is a contract describing how the design must behave, but written in English it's just intent — ambiguous, and enforced only by humans reading it. Assertion-based verification turns each contract clause into an enforceable, machine-checkable statement — a formal property — and embeds it with the design, so the contract is enforced automatically instead of trusted. Who drafts each clause depends on who knows it: the designer drafts the internal clauses about the micro-architecture's own assumptions, the verifier drafts the external clauses about the protocol the design presents to the world. And the contract can be enforced two ways: you spot-check it against the transactions you actually run (dynamic simulation — fast, but only as thorough as what you exercise), or you prove it holds for every possible input (formal — exhaustive, but bounded by the tool's reach). A contract you only spot-check is enforced where you looked; a contract you prove is enforced everywhere — and mistaking the first for the second is how a clause-violating case slips through unlooked-at. Picture the specification as a contract describing how the design must behave. Written in English, it's just intent — ambiguous, and enforced only by humans reading it. ABV turns each contract clause into an enforceable, machine-checkable statement — a formal property — and embeds it with the design, so the contract is enforced automatically instead of trusted. Who drafts each clause depends on who knows it: the designer drafts the internal clauses (the micro-architecture's own assumptions — this FIFO never overflows, this state is one-hot), the verifier drafts the external clauses (the protocol the design presents to the world). And the contract can be enforced two ways: you spot-check it against the transactions you actually run (dynamic simulation — fast, but only as thorough as what you exercise), or you prove it holds for every possible input (formal — exhaustive, but bounded by the tool's reach). A contract you only spot-check is enforced where you looked; a contract you prove is enforced everywhere — and mistaking the first for the second is how a clause-violating case slips through unlooked-at (the dynamic-isn't-proof trap). The scoreboard, by contrast, is not a clause-checker — it's the auditor computing the expected final result; you use clauses (assertions) for the protocol and invariant rules, and the auditor (scoreboard) for the end-to-end data computation.
So ABV is turning the spec's contract clauses into enforceable, machine-checkable form, embedded with the design: unambiguous (formal), enforced automatically (every run), self-documenting (the clause is the rule), drafted by whoever knows it (design: internal invariants; DV: external protocol), and enforced two ways — spot-checked on the runs (dynamic, thorough-as-your-stimulus) or proven for all inputs (formal, exhaustive). Don't mistake spot-checking for proof, and use clauses for rules, the auditor (scoreboard) for end-to-end computation. Make the spec's clauses enforceable and embedded, draft each by who knows it, and know whether you spot-checked or proved.
Visual Explanation — assertions as executable specification
The defining picture is the transformation: an English spec rule (ambiguous, human-checked) becomes a formal assertion (unambiguous, machine-checked, self-documenting, design-resident).
The figure shows the transformation of a spec rule into executable specification. An English spec rule (the warning-colored start — ambiguous, human-checked) becomes a formal assertion (the brand-colored center — executable specification) with four properties: unambiguous (a precise property, not open to interpretation), checkable (verified automatically every run, not by human review), self-documenting (the property is the rule — reading it tells you the intended behavior), and design-resident (lives with the RTL and travels with it). The crucial reading is what each property buys. Unambiguous: an English rule like "the grant must follow the request promptly" has no precise meaning (how prompt?), but req |-> ##[1:3] ack is exact — the formalization forces precision (and exposes spec ambiguities). Checkable: the rule is enforced automatically on every simulation (and formally), not dependent on a human remembering to check it. Self-documenting: the assertion documents the exact intended behavior — a new engineer reading the assertions learns the protocol (the assertions are the spec, executable). Design-resident: the assertion lives with the design (bound or inline), so it travels with the RTL across reuse, refactors, and projects — the contract follows the design. The warning-colored ambiguous spec maps through the brand-colored assertion to the success-colored properties — and together they make the assertion executable specification: it both documents and enforces the intended behavior, bridging the gap between the specification (intent) and the implementation (RTL). This is the central idea of ABV: the spec stops being a document humans interpret and becomes a contract the machine enforces. The diagram is the executable-spec transformation: English rule → formal assertion → (unambiguous, checkable, self-documenting, design-resident) — the spec made executable. An assertion is the spec rule made formal — it documents and enforces the intended behavior at once.
RTL / Simulation Perspective — who writes what, and the two engines
In code, who writes an assertion follows what it checks (internal vs external), and the same assertion serves both simulation and formal. The example shows design and DV assertions, and the dynamic/formal duality.
// === DESIGN-WRITTEN: white-box assertions on INTERNAL invariants (caught early, at the source) ===
module fifo(...);
// the designer knows the micro-architecture — these are INTERNAL assumptions
// State the invariant directly. The `|-> 0` form some guides use is legal,
// but it reads as a puzzle and is one keystroke away from a consequent that
// can never fail.
a_no_overflow: assert property (@(posedge clk) disable iff (!rst_n)
!(wr_en && full)) // never write when full
else $error("FIFO overflow");
// `disable iff` is not decoration here: during reset `state` is 0 or X, and
// $onehot is false for both. Without it this fires on every reset of every
// test, someone waives it, and the waiver then hides the real failures.
a_ptr_onehot: assert property (@(posedge clk) disable iff (!rst_n)
$onehot(state)); // FSM state is one-hot
// a violation FIRES at the EXACT internal event → debugged at the SOURCE, during design
endmodule
// === VERIFICATION-WRITTEN: black-box assertions on the EXTERNAL protocol (the spec contract) ===
module bus_protocol_checker(...); // bound to the DUT (Module 21.1)
a_handshake: assert property (@(posedge clk) disable iff (!rst_n)
req |-> ##[1:3] ack); // external protocol rule
a_mutex_grant: assert property (@(posedge clk) disable iff (!rst_n)
!(grant0 && grant1)); // spec: one grant at a time
endmodule
// === THE SAME ASSERTION, TWO ENGINES ===
// DYNAMIC (simulation): a_handshake is checked on the stimulus you RUN
// → catches violations IN THE PATHS EXERCISED — scenario-bound (only as good as the stimulus)
// FORMAL (property checking): a tool PROVES a_handshake holds for ALL inputs, or finds a counterexample
// → EXHAUSTIVE — no stimulus needed — but bounded by complexity/convergence
// ✗ MISTAKE: a_handshake PASSES in simulation → "the protocol is proven" → NO. Only the exercised paths
// were checked; an un-driven path may hide a latent violation (DebugLab) — cover + formal-proveThe split is what matters. Design-written assertions are white-box, on internal invariants: a_no_overflow and a_ptr_onehot encode micro-architectural assumptions only the designer knows, and a violation fires at the exact internal event — debugged at its source, during design, rather than later when it has already corrupted an output the scoreboard sees. Verification-written assertions are black-box, on the external protocol: a_handshake and a_mutex_grant encode the interface contract from the spec, bound to the DUT without touching its RTL.
The same assertion then serves two engines. Dynamic checking evaluates it against the stimulus you actually run, so it catches violations on exercised paths and says nothing about the rest. Formal property checking proves it for all inputs or returns a counterexample — exhaustive and stimulus-free, but bounded by convergence. Write once, check two ways.
That duality is also where the cardinal mistake lives, and it is the subject of the Debug Lab below: a_handshake passing in simulation does not mean the protocol is proven. It means the paths your tests drove did not violate it.
Wiring the Checker Into UVM — where an assertion failure actually gets counted
The bus_protocol_checker above is only half the integration. Writing the
properties themselves is covered in concurrent assertions
and, for this bus, in AHB assertions; what follows
is the part that decides whether anyone finds out when one fires. An assertion that
fires is worthless if nothing downstream notices, and the default $error action
block is exactly that hazard: $error is a SystemVerilog severity task, not a
UVM one. It prints, and it increments the simulator's error count — but the
UVM report server never sees it. uvm_report_summarize() at the end of the test
still prints UVM_ERROR : 0, set_max_quit_count never trips, and any regression
script that decides pass/fail from the UVM error count marks the test PASSED
while the transcript above it is full of protocol violations.
Route the action block through UVM instead. The checker below is complete enough to bind and run:
// Compiled as a standalone module, bound into the DUT. It imports uvm_pkg only
// to report; it instantiates nothing, extends nothing, and joins no phase.
module ahb_protocol_checker (
input logic HCLK,
input logic HRESETn,
input logic [1:0] HTRANS,
input logic HWRITE,
input logic [31:0] HADDR,
input logic HREADY,
input logic [1:0] HRESP
);
import uvm_pkg::*;
`include "uvm_macros.svh"
localparam logic [1:0] IDLE = 2'b00, BUSY = 2'b01, NONSEQ = 2'b10, SEQ = 2'b11;
// One place to write the reporting call. `uvm_file and `uvm_line expand to
// `__FILE__ / `__LINE__, so the report points at the failing assertion rather
// than at this macro.
`define SVA_ERR(ID, MSG) \
uvm_report_error(ID, MSG, UVM_NONE, `uvm_file, `uvm_line)
// AMBA AHB: during a wait state (HREADY low) the address-phase controls must
// hold. `disable iff` handles the reset window; it does not need $assertoff.
a_addr_stable_in_wait: assert property (
@(posedge HCLK) disable iff (!HRESETn)
(HTRANS inside {NONSEQ, SEQ} && !HREADY)
|=> $stable(HADDR) && $stable(HWRITE) && $stable(HTRANS)
) else `SVA_ERR("AHB_SVA/ADDR_UNSTABLE",
$sformatf("address phase changed during a wait state: HADDR %0h->%0h, HWRITE %0b->%0b",
$past(HADDR), HADDR, $past(HWRITE), HWRITE));
// A two-cycle ERROR response must be preceded by HRESP=ERROR with HREADY low.
a_error_two_cycle: assert property (
@(posedge HCLK) disable iff (!HRESETn)
(HRESP == 2'b01 && HREADY) |-> $past(HRESP == 2'b01 && !HREADY)
) else `SVA_ERR("AHB_SVA/ERROR_ONE_CYCLE",
"ERROR response completed in one cycle; AHB requires the two-cycle sequence");
// The antecedent coverage that makes the pass meaningful. Without these, a
// green run cannot distinguish "checked and held" from "never evaluated".
c_wait_state_seen: cover property (
@(posedge HCLK) disable iff (!HRESETn)
HTRANS inside {NONSEQ, SEQ} && !HREADY);
c_error_resp_seen: cover property (
@(posedge HCLK) disable iff (!HRESETn)
HRESP == 2'b01 && !HREADY ##1 HRESP == 2'b01 && HREADY);
`undef SVA_ERR
endmodule
// Bound from the testbench: no DUT edit, no port list change, and the checker
// can be excluded from synthesis by simply not compiling this file.
bind ahb_slave ahb_protocol_checker u_ahb_sva (
.HCLK(HCLK), .HRESETn(HRESETn), .HTRANS(HTRANS), .HWRITE(HWRITE),
.HADDR(HADDR), .HREADY(HREADY), .HRESP(HRESP)
);Three things in that file carry their weight.
uvm_report_error instead of $error. It is a global function in uvm_pkg,
callable from a module — you cannot use the `uvm_error macro here, because
that macro resolves an implicit uvm_report_object that a module does not have.
The global function has no such dependency. Because the call is a void function
with no timing controls, it is legal in an assertion action block, which executes
in the Observed region when the property fails.
A structured message ID. "AHB_SVA/ADDR_UNSTABLE" is what a
uvm_report_catcher matches on, so a known-failing check can be demoted to a
warning for one test without editing the checker or commenting the assertion out.
An $error string offers nothing to hook.
cover property beside each assert property. These are the antecedent
counters that answer the question the Debug Lab below is about — the
assertion/coverage pairing in full. a_error_two_cycle
passing tells you nothing on its own; a_error_two_cycle passing while
c_error_resp_seen has a non-zero count tells you the check ran and held.
Assertions fired for six weeks and every regression stayed green
UVM-REPORT-BYPASSA block-level environment carried a bound protocol checker with roughly forty
assertions, all with else $error(...) action blocks. The nightly regression
reported 100% pass for six weeks. A designer investigating an unrelated waveform
opened a transcript by hand and found several hundred assertion failures in a run
the dashboard listed as PASSED.
The regression's pass/fail criterion came from UVM: final_phase queried
uvm_report_server::get_server().get_severity_count(UVM_ERROR) and the script
parsed the UVM_ERROR : line of uvm_report_summarize(). $error never touches
that path — it is a SystemVerilog severity task handled by the simulator, so the
UVM error count stayed at zero no matter how many assertions fired. Nothing was
broken in the assertions themselves; they were correct, they fired, and the
failures they reported were real. The reporting channel simply did not connect
to the channel that decided the verdict.
// ✗ fires, prints, and is invisible to the verdict
a_addr_stable: assert property (p_addr_stable)
else $error("address changed during wait state");
// ✓ the same failure, counted by the UVM report server, catchable, demotable
a_addr_stable: assert property (p_addr_stable)
else uvm_report_error("AHB_SVA/ADDR_UNSTABLE",
"address changed during wait state",
UVM_NONE, `uvm_file, `uvm_line);The durable fix is a project-wide macro so no checker can regress to $error,
plus a regression check that fails a run whose transcript contains simulator-level
errors even when UVM_ERROR is zero. Two independent verdict sources are what
stop a silent channel from deciding a sign-off.
Verification Perspective — assertions versus the scoreboard
The methodology decision is when to use an assertion versus the scoreboard — and forcing the wrong tool is a common error. Seeing the trade-off clarifies the complementary roles.
The figure shows the assertion-versus-scoreboard trade-off. Assertions fit local, temporal, protocol and invariant rules: a handshake window, a stability hold, a one-hot state, a mutual-exclusion rule — checks that are precise, fire early at the exact cycle, are cheap, and are amenable to formal proof. The scoreboard fits end-to-end data computation: the transformed output of a datapath, ordering across the whole DUT, anything needing a reference model to compute the expected result. The verification insight is that each tool has a natural domain, and forcing the wrong one fails: forcing a complex data computation into an assertion is unwieldy (an assertion can't easily compute the expected output of a multi-stage datapath — that's a reference model's job, and expressing it in SVA would be monstrous); forcing a protocol timing rule into the scoreboard misses it (the scoreboard's transaction abstraction can't see the cycle-by-cycle handshake — Module 21.1). So the decision rule is: is this a local/temporal rule about signal behavior (→ assertion) or an end-to-end computation of expected data (→ scoreboard)? A handshake, a hold, a one-hot, an ordering window → assertion (precise, early, cheap, formal-amenable). A datapath result, a transformed payload, a cross-DUT ordering of data → scoreboard (reference model). The warning-colored "rule to check" routes to the brand-colored tool by its nature, yielding the success-colored fit. This is the methodology's tool-choice: not "assertions vs scoreboard" as rivals, but complementary tools each best for a class of check — and using each for what it's good at (and not forcing the wrong one) is part of ABV as a methodology. The diagram is the tool-choice: local/temporal/protocol → assertion, end-to-end data computation → scoreboard — complementary domains, each for what it's good at. Assertions for temporal/local rules, the scoreboard for end-to-end data — don't force either into the other's domain.
Runtime / Execution Flow — ABV layered into the complete strategy
At the strategy level, ABV is one layer of a complete checking flow — combined with CRV, the scoreboard, and coverage, spanning dynamic and formal. The flow shows the integration.
The flow shows ABV layered into the complete strategy. CRV (step 1): the coverage-driven constrained-random flow (Module 20) generates and steers the stimulus. Check (step 2): on every transaction, assertions verify protocol/invariant rules (precisely, early) and the scoreboard verifies end-to-end function. Measure (step 3): coverage — functional coverage plus assertion coverage (antecedents fired) — measures thoroughness (Modules 19, 21.5). Formal (step 4): the same assertions, formally proven for all inputs — exhaustive coverage of the critical properties. The runtime insight is that ABV is not a standalone methodology — it's one layer of a complete checking strategy that combines: CRV (stimulus), assertions (protocol/invariants — this module), scoreboard (function — Module 18), coverage (thoroughness — Module 19), and formal (exhaustiveness). Each covers a role: CRV drives, assertions catch protocol/invariant bugs precisely and early, the scoreboard catches functional data bugs end-to-end, coverage measures completeness (including assertion coverage — did the assertions actually fire their checks), and formal proves the critical properties exhaustively (beyond what any stimulus exercises). The power of the combination is defense in depth across abstractions and methods: a bug escapes only if it evades all the layers. And the dynamic/formal span is key: dynamic ABV (in the CRV flow) catches what the stimulus exercises; formal ABV proves the critical properties for all inputs — together covering both the exercised and the un-exercised space. So ABV contributes the protocol/invariant checking and the formal exhaustiveness to the whole. The flow is the complete strategy: CRV (drive) → assertions + scoreboard (check) → coverage (measure) → formal (prove) — ABV as one integrated layer, spanning dynamic and formal. ABV layers into the whole: assertions for protocol and invariants, scoreboard for function, coverage for completeness, formal for exhaustiveness.
Waveform Perspective — dynamic ABV is only as good as the stimulus
The dynamic-versus-formal distinction is visible on a timeline: a dynamic assertion only checks where the stimulus drives its antecedent — an un-driven path leaves it unchecked (a latent violation hidden), while formal would prove it everywhere. The waveform shows it.
A dynamic assertion checks only where the stimulus drives its antecedent; an un-driven path hides a latent violation
12 cyclesThe waveform shows that dynamic ABV is scenario-bound. The handshake assertion (req |-> ack within the window) only does real work when req is driven. Where the stimulus drives req (the checked windows), the assertion actively checks and passes. But in a path the stimulus never drives req (the vacuous region), the assertion passes vacuously — it checks nothing there. A latent violation in that un-driven path (latent_bug) is never triggered by simulation, so the dynamic assertion passes overall while the bug hides. Formal property checking would prove the assertion for all inputs, catching the latent violation the stimulus never reached (formal_only). The crucial reading is the fundamental limit of dynamic ABV: an assertion in simulation is only checked where the stimulus exercises its antecedent — and where the antecedent is never driven, the assertion passes vacuously, providing zero protection there. So an all-passing assertion suite in simulation means "no violation in the paths the stimulus exercised", not "the property holds for all inputs" — and a latent violation in an un-exercised path ships, despite the passing assertions. This is exactly the coverage lesson (Module 19.1) applied to assertions: passing isn't proven unless you exercised the scenario — which is why covering the antecedents (knowing which assertions actually fired) and formally proving the critical ones are part of ABV. The picture to carry is that dynamic ABV inherits the stimulus's blind spots: it's as good as what you run, no better — while formal is exhaustive (it proves for all inputs, independent of stimulus). Reading the waveform this way — is the assertion actively checking, or vacuously passing because the stimulus never drove its antecedent? — is understanding dynamic ABV's limit. The vacuous region hiding a latent bug that formal would catch is the signature of the dynamic-isn't-proof truth: simulation checks where you looked; formal proves everywhere. A dynamic assertion is only as good as the stimulus that drives its antecedent — passing in simulation is not proof; cover the antecedents and formally prove the critical properties.
DebugLab — the all-passing assertion suite that wasn't a proof
A protocol declared proven by passing assertions that never exercised the failing path
A team had an extensive protocol assertion suite — dozens of carefully-written, spec-faithful assertions — and a clean regression: every assertion passing, millions of cycles, green. In the sign-off review, the team declared the protocol "proven correct by the assertions." In silicon, a protocol bug surfaced: under a specific, rare sequence — a back-to-back request during a retry, a path the constrained-random stimulus happened never to generate — the DUT violated the handshake rule. The assertion for that rule existed and was correct — but it had passed vacuously throughout the regression, because its antecedent (the retry-then-back-to-back sequence) was never driven. The "proven" protocol had a hole the passing assertions never touched: a real violation in a path simulation never reached.
The team mistook passing assertions in simulation for a proof — but dynamic ABV only checks the paths the stimulus exercises, so an assertion whose antecedent was never driven passed vacuously, providing no protection for the un-exercised path where the violation lived:
✗ TREAT passing assertions in SIMULATION as a proof:
// dozens of spec-faithful assertions, all PASSING, millions of cycles → "protocol proven correct"
// BUT: the assertion for the retry-back-to-back rule passed VACUOUSLY — its antecedent was
// NEVER driven by the random stimulus → it checked NOTHING on that path
// a real violation on that un-exercised path → simulation never triggered it → SHIPS
// dynamic ABV is only as good as the stimulus; passing != proven
✓ COVER the antecedents + FORMALLY prove the critical assertions:
// (a) cover property for each assertion's antecedent → know which assertions ACTUALLY fired (21.5)
// the retry-back-to-back cover would show 0 hits → the assertion never checked → a GAP, flagged
// (b) formally prove the critical protocol assertions → exhaustive, all inputs, no stimulus needed
// formal would find the retry-back-to-back counterexample → caught before tapeout
// passing in sim = "no violation in exercised paths"; proven = formal or covered-and-exercisedThis is the dynamic-isn't-proof bug — the cardinal ABV-methodology failure. The team had a correct, extensive assertion suite, all passing — and concluded the protocol was "proven correct." But passing in simulation means "no violation in the paths the stimulus exercised", not "the property holds for all inputs." The assertion for the retry-back-to-back rule existed and was correct, but its antecedent (the specific sequence) was never driven by the random stimulus, so it passed vacuously — checking nothing on that path. The real violation lived on exactly that un-exercised path, so simulation never triggered it, and the passing assertions gave false confidence that the protocol was fully verified. The root error is conflating dynamic checking with formal proof: dynamic ABV is only as good as the stimulus, and an assertion's protection extends only to where its antecedent is exercised. The fix is two-fold. (a) Cover the antecedents: a cover property for each assertion's antecedent shows which assertions actually fired their checks (Module 21.5) — the retry-back-to-back cover would show 0 hits, flagging the gap (the assertion never checked, so the path is unverified). (b) Formally prove the critical assertions: formal property checking is exhaustive (all inputs, no stimulus), so it would find the retry-back-to-back counterexample — caught before tapeout. The general lesson, and the chapter's thesis: passing assertions in simulation are not a proof — dynamic ABV only checks the paths the stimulus exercises, so an all-passing assertion suite means "no violation in the exercised scenarios", not "the protocol holds for all inputs", and a latent violation in an un-driven path ships despite the passing assertions; so cover the antecedents (to know which assertions actually fired, not vacuously passed) and formally prove the critical properties (for exhaustive, stimulus-independent coverage) — because simulation checks where you looked, and only formal proves everywhere. An assertion that passes is only as trustworthy as the stimulus that exercised its antecedent — cover what fired, prove what's critical, and never call simulation a proof.
The tell is assertions declared a proof without antecedent coverage or formal. Diagnose dynamic-mistaken-for-proof:
- Check antecedent coverage. For each critical assertion, confirm its antecedent was actually exercised; a 0-hit antecedent cover means the assertion never checked.
- Distinguish vacuous from real passes. A passing assertion whose antecedent never fired passed vacuously and verified nothing on that path.
- Identify un-exercised protocol paths. Rare sequences the random stimulus may not generate are where latent violations hide; map them against coverage.
- Ask whether critical properties were formally proven. Simulation passing is scenario-bound; only formal proves a property for all inputs.
Cover what fired and prove what's critical:
- Cover every assertion's antecedent. A cover property confirms the assertion actually fired its check, so passes are meaningful (Module 21.5).
- Formally prove the critical protocol assertions. Property checking is exhaustive and stimulus-independent, catching latent violations simulation never reaches.
- Never equate passing in simulation with proven. Passing means no violation in exercised paths; reserve proven for formal or covered-and-exercised.
- Steer stimulus toward un-checked antecedents. Treat a 0-hit antecedent as a coverage gap and steer the CRV to exercise it (Module 20.4).
The one-sentence lesson: passing assertions in simulation are not a proof — dynamic ABV only checks the paths the stimulus exercises, so an all-passing suite means no violation in the exercised scenarios, not that the protocol holds for all inputs, and a latent violation in an un-driven path ships despite the passing assertions, so cover the antecedents to know which assertions actually fired and formally prove the critical properties for exhaustive coverage.
Common Mistakes
- Equating passing assertions in simulation with proof. Dynamic ABV only checks exercised paths; cover antecedents and formally prove critical properties for real assurance.
- Not covering antecedents. A passing assertion whose antecedent never fired verified nothing; cover property confirms it actually checked.
- Forcing a data computation into an assertion. Complex end-to-end expected-output computation belongs in a scoreboard reference model, not an unwieldy SVA property.
- Forcing a protocol timing rule into the scoreboard. The transaction abstraction can't see cycle-by-cycle protocol; use an assertion for it.
- Skipping design-written assertions. Internal invariants caught only at the interface are debugged late and far from the source; design should assert internal invariants.
- Treating assertions and the scoreboard as rivals. They're complementary tools for different check classes; use each for what it's good at.
Senior Design Review Notes
Interview Insights
Assertion-based verification is the methodology of capturing the design's intended behavior as assertions and using them throughout the verification flow, and assertions are called executable specification because they are the spec's rules written formally so they can be both documented and automatically enforced. The core idea is that a specification written in English is just intent — it's ambiguous and enforced only by humans reading it. An assertion turns a spec rule into a formal property that has four key qualities. It's unambiguous: where English might say the grant must follow the request promptly, an assertion says exactly req implies ack within one to three cycles, and the act of formalizing forces precision and often exposes spec ambiguities. It's checkable: the rule is verified automatically on every run, not dependent on a human remembering to check it. It's self-documenting: the property is the rule, so reading the assertions teaches you the protocol — the assertions are the spec, in executable form. And it's design-resident: the assertion lives with the RTL and travels with it across reuse, refactors, and projects, so the contract follows the design. Put together, the assertion both documents the intended behavior and enforces it, bridging the gap between the specification and the implementation. That's why it's executable specification — the spec stops being a document humans interpret and becomes a contract the machine enforces. ABV as a methodology then encompasses who writes these assertions, how they're checked, and how they fit the overall strategy. The mental model is turning the spec's contract clauses into enforceable, machine-checkable statements embedded with the design — each clause drafted by whoever knows it, enforced automatically rather than trusted to human review. This is a step up from treating assertions as just another checker: it reframes them as the formal, executable encoding of the design's intended behavior, which is the foundation for both simulation checking and formal proof.
Both, and who writes a given assertion follows what it checks: design engineers write white-box assertions on internal invariants, and verification engineers write black-box assertions on the external protocol. The principle is that the right author is whoever knows the rule. Design engineers know the micro-architecture — the internal assumptions and invariants that their implementation relies on. So they write assertions about internal things: this FIFO never overflows, this state machine is always one-hot, this internal counter never wraps unexpectedly, this internal handshake between sub-blocks obeys its contract. These are white-box because they reference internal signals only the designer knows. The huge value is that a design assertion fires at the exact internal event where the invariant breaks, so the bug is caught early, at its source, during design — rather than late, when the internal problem eventually propagates to an output that the scoreboard sees, far from the cause and harder to debug. Verification engineers know the external specification — the protocol the design presents to the world, the interface contract. So they write black-box assertions on the external behavior: the handshake timing, mutual exclusion of grants, ordering rules, the spec's protocol. These are bound to the DUT non-intrusively and check the external contract. Both contribute, and together they form layered assertion coverage: design's internal invariants plus verification's external protocol. A design with only verification's external assertions catches internal bugs only when they reach the interface — late and indirect. A design with only internal assertions misses the external contract verification owns. So the methodology wants both. There's a collaboration aspect too: sometimes design and verification review each other's assertions, and the internal assumptions a designer asserts become documented contracts that verification can rely on. The key takeaway is that authorship isn't a turf question — it's matched to knowledge: internal invariants to design who knows the micro-architecture, external protocol to verification who knows the spec, with both layers making the assertion coverage complete.
Dynamic checking evaluates assertions during simulation on the stimulus you actually run, catching violations only in the paths exercised, while formal checking uses a property-checking tool to prove an assertion holds for all possible inputs or find a counterexample, exhaustively and without stimulus. The same assertion can serve both engines, which is a major strength of ABV. In dynamic checking, the simulator evaluates the assertion every clock as the testbench drives stimulus. If a violation occurs in a scenario you exercise, the assertion fires. But it's scenario-bound: the assertion only does real work where the stimulus drives its antecedent, and a path you never exercise leaves the assertion passing vacuously, checking nothing there. So dynamic ABV is only as good as the stimulus — passing means no violation in the exercised paths, not that the property holds universally. In formal checking, a tool mathematically analyzes the design and either proves the property holds for every reachable state and input, or produces a concrete counterexample trace showing a violation. It's exhaustive — it doesn't need stimulus, it explores the entire input space symbolically — so it catches latent violations on paths simulation never reaches. The trade-off is that formal is bounded by complexity and convergence: for large or deeply-stateful designs, the proof may not converge in reasonable time, so formal is typically applied to critical, bounded properties rather than everything. The practical implication is that they're complementary. You run assertions dynamically in the constrained-random flow for broad, cheap checking across realistic scenarios, and you formally prove the critical protocol properties for exhaustive assurance on the things that must never fail. The dangerous mistake is treating dynamic passing as a proof — an all-passing assertion suite in simulation is not the same as formally proven, because the stimulus may never have exercised the failing path. So you cover the antecedents to know which assertions actually fired, and you formally prove the critical ones. The slogan is: simulation checks where you looked, formal proves everywhere.
Use an assertion for local, temporal, protocol and invariant rules, and a scoreboard for end-to-end data computation that needs a reference model — they're complementary tools for different classes of check, and forcing the wrong one fails. Assertions excel at rules about signal behavior over time that are local and checkable cycle-by-cycle: a handshake completing within a window, a signal staying stable until a condition, a state being one-hot, two grants never both asserted, an ordering relationship. These are precise, fire early at the exact cycle of the violation for immediate debug, are cheap to evaluate, and are amenable to formal proof. The scoreboard excels at end-to-end functional correctness where you need to compute what the expected result should be: the transformed output of a datapath, a payload that's been processed, ordering of data across the whole DUT — anything requiring a reference model to independently compute the expected value and compare. The decision rule is to ask whether the check is a local temporal rule about signal behavior, which goes to an assertion, or an end-to-end computation of expected data, which goes to a scoreboard. Forcing the wrong tool fails in both directions. Forcing a complex data computation into an assertion is unwieldy — you can't reasonably express a multi-stage datapath's expected output as an SVA property; that's a reference model's job, and trying makes a monstrous, unmaintainable assertion. Forcing a protocol timing rule into the scoreboard misses it — the scoreboard works at the transaction abstraction and can't see the cycle-by-cycle handshake, so the protocol violation is invisible to it. So they're not rivals competing for the same checks; they're specialists. A mature environment uses assertions for the protocol and invariant layer and the scoreboard for the functional data layer, each catching the class of bug it's suited to and each covering the other's blind spot. Part of ABV as a methodology is making this tool choice correctly — recognizing whether a given rule is a temporal/local property or an end-to-end data computation, and putting it where it belongs rather than forcing it into the wrong mechanism.
ABV is one layer of a complete checking strategy that combines constrained-random stimulus, assertions, the scoreboard, coverage, and formal — each playing a distinct role, with assertions contributing the protocol and invariant checking plus formal exhaustiveness. In the integrated flow, constrained-random drives and steers the stimulus. On every transaction, multiple things happen in parallel: assertions verify the protocol and invariant rules continuously and precisely, firing at the exact cycle of any violation; the scoreboard verifies end-to-end functional correctness by comparing against a reference model; and coverage measures thoroughness — both functional coverage of scenarios and assertion coverage, meaning whether the assertions' antecedents actually fired so their passes are meaningful. Alongside the dynamic flow, the same assertions can be formally proven for exhaustive coverage of the critical properties, independent of stimulus. So the complete picture is constrained-random for stimulus, assertions for protocol and invariants, scoreboard for end-to-end function, coverage for completeness, and formal for exhaustiveness. The power of combining them is defense in depth across abstractions and methods — a bug escapes only if it evades all the layers. Assertions specifically add two things to the whole. First, the protocol and invariant layer: the cycle-by-cycle signal-level rules the transaction-level scoreboard can't see, caught precisely and early, including design's internal invariants caught at their source. Second, the formal dimension: because assertions are formal properties, the critical ones can be proven exhaustively, covering the un-exercised space that dynamic simulation, bound by its stimulus, can't reach. The coverage methodology extends to assertions too — covering antecedents tells you the assertions did real work, and a 0-hit antecedent is a gap to steer stimulus toward, exactly like a functional coverage gap. So ABV isn't a separate methodology competing with coverage-driven constrained-random; it's a layer woven into it, adding precise protocol checking and formal exhaustiveness to the scoreboard's functional checking and coverage's completeness measurement, the whole forming a comprehensive checking strategy.
Almost always, the assertion's action block used $error while the pass/fail verdict came from UVM's error count. $error is a SystemVerilog severity task: the simulator prints it and counts it in its own error tally, but it never reaches the UVM report server. So uvm_report_summarize() still prints UVM_ERROR : 0, set_max_quit_count never trips, and a regression script that greps the UVM summary or a final_phase check that calls get_severity_count(UVM_ERROR) sees a clean run. The failures are real and printed, and the verdict is computed from a channel that cannot see them. The fix is to report through UVM: call the global uvm_report_error function from the action block, passing the message ID, the text, UVM_NONE, and the file/line macros. Note that you cannot use the `uvm_error macro inside a module, because that macro resolves an implicit uvm_report_object a module does not have; the global function has no such dependency, and it is legal in an action block because it is a void function with no timing controls. Beyond the immediate fix, two habits prevent recurrence. Wrap the call in a project-wide macro so no checker can quietly regress to $error. And give the regression a second, independent verdict source that fails a run whose transcript contains simulator-level errors even when the UVM error count is zero — a single reporting channel deciding sign-off is the underlying fragility, not the one bad action block.
Because assertions evaluate from time zero, and during reset the DUT's signals are legitimately in states the property forbids. A one-hot state check is the clearest case: while reset is asserted the state register is all-zeros or X, and $onehot is false for both, so the assertion fires on every reset of every test. What happens next is the real damage. Someone waives it, or filters it from the log, or comments the assertion out — and that waiver then hides genuine one-hot violations later in the run. disable iff (!rst_n) states the intent precisely: this property is not meaningful while reset is active. It is scoped to the one property, it is visible in the source next to the check it qualifies, and it turns itself off and on with the reset signal automatically. That makes it the default choice, and it is why a checker written without it tends to accumulate waivers instead of accumulating trust. $assertoff is a different instrument, and its strengths are exactly where disable iff is awkward. It acts on a whole scope hierarchically, so it suits switching off an entire block's assertions for a directed test that deliberately drives illegal stimulus — an error-injection test, or a test whose whole purpose is to confirm the DUT's recovery from a protocol violation. Its danger is procedural: it is a task call made at some point in time, and if nothing calls $asserton afterward the assertions stay off for the rest of the simulation, silently. A run with a forgotten $asserton looks identical to a run with no bugs. So the working rule is disable iff for conditions inherent to the property, especially reset, and $assertoff/$asserton as a deliberate, paired, test-scoped override — never as the routine way to handle the reset window.
It distinguishes "the check ran and held" from "the check never evaluated", which the assert result alone cannot express. Both outcomes report as a pass. An implication whose antecedent never becomes true passes vacuously on every clock, and it will sit in a green regression indefinitely looking exactly like a property that is actively protecting you. That is the failure in this lesson's Debug Lab: a correct assertion for a real rule, passing for weeks, protecting nothing, because the stimulus never generated the sequence that would have engaged it. A cover property on the antecedent turns that invisible state into a number. If the assert passes and its cover has a non-zero hit count, the check did work. If the assert passes and its cover is at zero, you have learned something more valuable than a pass — you have found a stimulus gap, and it is exactly the same signal a zero-hit functional coverage bin gives you. The response is the same too: either steer the constrained-random stimulus to generate the sequence, or write a directed test for it, and if neither is feasible, that property is a candidate for formal proof, since formal does not depend on stimulus reaching the antecedent at all. The methodological point is that assertion coverage is coverage. A team that reports assertion pass rates without antecedent coverage is reporting a number that cannot go down and therefore carries no information; pairing every non-trivial assert with a cover is what makes the pass rate mean something.
Exercises
- Classify the authorship. For a FIFO-overflow invariant and an external handshake rule, say who writes each assertion and why.
- Dynamic versus formal. Explain why an all-passing assertion suite in simulation is not a proof, and what makes it one.
- Choose the tool. For a one-hot state check and a datapath-output check, pick assertion or scoreboard for each and justify.
- Close the gap. An assertion's antecedent cover shows 0 hits. Explain what that means and the two ways to address it.
Where This Is Specified
- IEEE 1800-2023 (SystemVerilog), clause 16 — Assertions. Concurrent and immediate assertions, the
assert/assume/coverproperty statements, action blocks, anddisable iff. §16.14.6 covers the assertion control tasks ($assertoff,$asserton,$assertkill). - IEEE 1800-2023, §22.13 — Source file and line macros.
`__FILE__and`__LINE__, which UVM's`uvm_file/`uvm_lineexpand to. - IEEE 1800-2023, §23.11 — Binding auxiliary code to scopes. The
bindconstruct that attaches a checker to a design instance without editing its source. - IEEE 1800.2-2020 (UVM) / Accellera UVM Reference — reporting. The global
uvm_report_error/uvm_report_warningfunctions,uvm_report_serverseverity counts,uvm_report_catcher, anduvm_report_summarize().
Summary
- Assertion-based verification (ABV) is the methodology of capturing the design's intended behavior as assertions — executable specification (unambiguous, checkable, self-documenting, design-resident) — and using them throughout the flow.
- Who writes them follows what they check: design writes white-box assertions on internal invariants (caught early, at the source), verification writes black-box assertions on the external protocol — both layers make the assertion coverage complete.
- How they're checked spans two engines: dynamic (simulation — scenario-bound: passing means no violation in the exercised paths, not proven) and formal (property checking — exhaustive: proven for all inputs or a counterexample); the same assertion serves both.
- ABV trades off with the scoreboard — assertions for local, temporal, protocol/invariant rules (precise, early, formal-amenable), the scoreboard for end-to-end data computation (reference model) — and layers into the CRV + coverage flow as one part of a complete checking strategy.
- A checker only counts if its failures reach the verdict:
$errorin an action block is a SystemVerilog severity task that the UVM report server never sees, so a run full of real assertion failures can still printUVM_ERROR : 0and pass. Report throughuvm_report_errorwith a structured ID, and pair every non-trivialassert propertywith acover propertyon its antecedent so a pass carries information. - The durable rule of thumb: treat assertions as executable specification — the spec's contract clauses made formal and enforceable — written by whoever knows the rule (design's internal invariants, DV's external protocol), used for local/temporal/protocol checks while the scoreboard handles end-to-end data, and checked by two engines; never mistake passing in simulation for proof, because dynamic ABV only checks the paths the stimulus exercises, so cover the antecedents to know what actually fired and formally prove the critical properties — simulation checks where you looked, formal proves everywhere.
Next — Assertion Debug: when an assertion fires, you have to debug it — and an assertion failure points precisely at a cycle and a rule, which is both its strength and its subtlety. The next chapter covers debugging assertion failures: reading the failure report and the property, finding the exact cycle and the signals involved, distinguishing a real DUT bug from a wrong or too-strict assertion, handling vacuous passes and antecedent-never-fired gaps, and the systematic process from a fired assertion to its true cause.
Continue learning
Related tutorials
- Related topic
SVA Integration
How SystemVerilog Assertions integrate into a UVM environment — assertions as a concurrent, signal-level temporal-checking layer; where they live and how bind attaches a checker to the DUT non-intrusively; how their failures route into UVM reporting and their cover property feeds the coverage picture; and how assertion-based checking complements the transaction-level scoreboard and functional coverage.
- Related topic
Assertion / Coverage Synergy
Uniting assertions and coverage — cover properties that measure whether assertions actually fired their checks, the two kinds of assertion coverage (antecedent and scenario), how assertion coverage feeds the closure picture, why a vacuous pass is exactly an assertion-coverage gap, and how assertions and coverage together close the loop on both correctness and completeness of the protocol checking.