Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
    • Implication and Property Operators
    • Sampled Value Functions
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • SVA Interview Drills
      • Handshake Drills
      • Pulse & Stability Drills
      • Ordering & Counting Drills
      • Data & Reset Drills
      • FSM & Arbiter Drills
      • Q&A: Assertion Fundamentals
      • Q&A: Operators & Semantics
      • Q&A: Vacuity, Debug & Formal
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹SVA Interview Drills
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. SVA Interview Drills
  5. Q&A: Vacuity, Debug & Formal

Part 4 · Assertions (SVA) · Intermediate

Q&A: Vacuity, Debug & Formal

Vacuous passes, cover pairing, weak versus strong, debug triage, and formal semantics.

On this page

    Why this matters

    A team signed off a mode-3 feature on a green assertion that had never seen mode 3. The paired cover read zero the whole time.

    After this lesson you can: explain vacuity and cover pairing, weak against strong properties, failure triage, and what formal proves that simulation cannot.

    Note

    Revision page. Answer each question before opening it; the full lessons are Vacuous Success and When Passing Is the Bug.

    Predict first An assert passes and its paired cover never hit. What does that say about the feature?

    Nothing. The assert passed vacuously: the antecedent never fired, so the consequent was never checked.

    Predict first Why do simulation regressions use ##[1:64] ack rather than s_eventually ack?

    A bounded window can fail during the run and states the spec's worst case. A strong eventually only fails at the end of simulation, and every legitimately in-flight request becomes end-of-test noise.

    Vacuity and covers

    A vacuous pass is an implication attempt whose antecedent never matched: the consequent was never tested, and the attempt counts as a pass. Per-attempt vacuity is healthy; idle cycles should pass. Whole-regression vacuity is a silent hole: every attempt vacuous, nothing checked, dashboard green.

    Three usual causes cover most cases: the stimulus never produced the scenario, a typo made the antecedent unsatisfiable, or reset and enable gating swallowed it. Encoding spec exceptions in the antecedent is the legitimate use: the write-readback drill routes overwritten cases into vacuity on purpose.

    What happens

    What the dashboard shows

    The testbench never drives grant_mode == 3

    The assertion: pass, with a large attempt count

    Every attempt: antecedent false

    Vacuous pass, every cycle

    Attempts that examined ack

    Zero

    Detection has four layers. Tool vacuity reporting separates pass-vacuous from pass-non-vacuous counts per assertion: demand non-vacuous above zero at signoff. Paired covers on the triggering scenario turn “did it ever check” into a metric. Mutation and fault injection break the RTL deliberately and confirm the assertion fires. In formal, prove the antecedent reachable: an unreachable cover exposes over-constraint or dead logic.

    Assert and cover answer orthogonal questions: was the rule ever violated, and was it ever exercised. The dangerous quadrant is assert-pass with cover-missed: invisible on any dashboard that shows only pass and fail. Pair every assert with covers on its antecedent and on its boundary constants: the fastest legal ack and the slowest, the minimum pulse and the maximum.

    Check yourself The assert passes and its paired cover never hits. The designer says the feature works. What do you conclude?

    Nothing about the feature. The assert is vacuously green and the cover proves the scenario never ran. The next step is stimulus that hits the cover, or a mutation test proving the assert can fire at all.

    Strong, weak, and bounded

    The difference is the verdict for unfinished attempts. A weak sequence or property still in progress at end of simulation is not a failure: the pending attempt is discharged without failing. A strong form, strong(seq), s_until, or s_eventually, demands completion and fails attempts that cannot complete.

    The listing below is not run in this course's checks: Verilator 5.048 does not support strong or s_eventually.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // not-run: Verilator 5.048 does not support strong or s_eventually
    a_weak: assert property (@(posedge clk) disable iff (!rst_n)
      $rose(req) |-> ##[1:$] ack);
    
    a_strong: assert property (@(posedge clk) disable iff (!rst_n)
      $rose(req) |-> strong(##[1:$] ack));
    
    a_live: assert property (@(posedge clk) disable iff (!rst_n)
      $rose(req) |-> s_eventually ack);

    Marking everything strong in simulation drowns the run in end-of-test noise: every legitimately in-flight transaction fails. The standing policy is bounded windows in simulation and strong forms in formal, generated as twins from one spec rule.

    Where

    Property

    Why

    Simulation regression

    $rose(req) |-> ##[1:64] (gnt || !req)

    64 is the arbiter's worst case from the spec; it runs every night and can fail

    Formal bench

    $rose(req) |-> s_eventually (gnt || !req), plus fairness assumes

    True liveness, proven for each RTL change

    A bounded proof guarantees no counterexample within K cycles of the initial state, and nothing beyond. Report it as clean to depth K with K stated next to the design's natural depths: FIFO depths, counter periods, timeout values. Failed induction means the property is not inductive, not that it is false: add strengthening invariants about reachable state until it closes, or accept a documented deep bound.

    Check yourself All assertions pass and the design is still broken. Name the gap classes.

    Specification gaps nobody wrote a property for, vacuity where properties never fired, weak forms that cannot fail in finite simulation, and observability limits of sampled clocked checks. Assertions are necessary conditions, never sufficient ones.

    Debug and assume discipline

    Triage before diving. Read the assertion as a spec sentence and locate the attempt start time: for a multi-cycle property the failure time is the end of the story, and the attempt began earlier by the property depth. Examine sampled values over that window in the assertion-debug view, never raw cursor reads.

    Start from the attempt's start time. Check the antecedent, then the consequent against the spec, then reset inside the window. If all three are sound, legal stimulus means an RTL bug and illegal stimulus means an environment bug or a missing assume.
    Triage for a failure at time T.

    A never-failing assertion earns trust through a four-step audit: non-vacuous counts above zero, boundary covers hit, firing on an injected bug, and a history of catching something in bring-up. Silence without that audit is unverified, not working.

    Simulation evaluates one finite stimulated trace: failures are real but coverage equals stimulus quality, weak pending attempts dissolve at the end, and X flows as 4-state values. Formal evaluates all reachable traces under the assumes: a pass is a proof, liveness is fully meaningful, and free inputs take every value at once. When formal fails what sim never failed, check the counterexample stimulus for legality first: a missing assume masquerades as a bug nine times out of ten.

    Each assume restricts the trace universe to behaviors satisfying it. Over-constraint removes the behavior containing the bug and the assert proves over the remainder: a false negative wearing a green checkmark. Contradictory assumes prove everything vacuously. Keep assumes honest with a cover sanity set that must stay reachable, a contradiction check every session, and one spec citation per assume.

    Simulation

    Formal

    Trace set

    One finite driven trace

    All reachable traces

    Pass means

    Not violated here

    Cannot be violated

    Weak pending at end

    Discharged quietly

    Not applicable, infinite traces

    s_eventually

    Fails at end of simulation if still pending (end-of-test noise), never mid-run

    Real verdict with fairness

    Inputs

    As driven

    Free unless assumed

    Count is a vanity metric for assertion suites. Report plan-trace ratio of spec rules mapped to properties, non-vacuity ratio across merged regression, and mutation kill rate on a sampled set. Density per line of code serves only as a smell test for blocks at zero.

    Testbench

    The bench runs the vacuity audit for real: four requests that never use mode 3, where the assert stays green and its cover reads zero, then two mode-3 requests, one acked late. A bounded-liveness check and its cover run beside them.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    module tb_qa_vacuity;
      logic clk = 0, rst_n = 0, req = 0, ack = 0;
      logic [1:0] grant_mode = 0;
      always #5 clk = ~clk;
      int fails = 0, ante_hits = 0, done_hits = 0;
      localparam int MAX = 8;
    
      // The assert and its paired cover on the antecedent: write them together.
      a_mode3: assert property (@(posedge clk) disable iff (!rst_n)
        (req && grant_mode == 3) |-> ##2 ack) else fails++;
      c_mode3: cover property (@(posedge clk) disable iff (!rst_n)
        req && grant_mode == 3) ante_hits++;
      // Bounded liveness: the simulation form of "every request eventually acked".
      a_bounded: assert property (@(posedge clk) disable iff (!rst_n)
        $rose(req) |-> ##[1:MAX] ack) else fails++;
      c_done: cover property (@(posedge clk) disable iff (!rst_n)
        $rose(req) ##[1:MAX] ack) done_hits++;
    
      task automatic request(logic [1:0] mode, int lat);
        @(negedge clk) req = 1; grant_mode = mode;
        @(negedge clk) req = 0;
        repeat (lat - 1) @(negedge clk);
        ack = 1; @(negedge clk) ack = 0;
        repeat (3) @(negedge clk);
      endtask
    
      initial begin
        repeat (2) @(negedge clk);
        rst_n = 1;
        repeat (4) request(1, 2);                    // mode 3 never used
        $display("4 requests in mode 1: a_mode3 %0d failures, its antecedent cover %0d hits (vacuous)", fails, ante_hits);
        if (fails != 0 || ante_hits != 0) $fatal(1, "FAIL: vacuous phase");
        request(3, 2);                               // mode 3, acked on time
        request(3, 3);                               // planted: mode 3, acked a cycle late
        $display("2 requests in mode 3: a_mode3 %0d failure, cover %0d hits; bounded liveness covered %0d requests", fails, ante_hits, done_hits);
        if (fails != 1 || ante_hits != 2 || done_hits != 6) $fatal(1, "FAIL: %0d %0d %0d", fails, ante_hits, done_hits);
        $display("PASS: the cover exposed the vacuous phase, and the assert caught the late ack once mode 3 was used");
        $finish;
      end
    endmodule
    Terminal
    4 requests in mode 1: a_mode3 0 failures, its antecedent cover 0 hits (vacuous)
    2 requests in mode 3: a_mode3 1 failure, cover 2 hits; bounded liveness covered 6 requests
    PASS: the cover exposed the vacuous phase, and the assert caught the late ack once mode 3 was used

    Remember

    • Green with a zero cover means unchecked.

    • Bounded windows in simulation; strong liveness in formal.

    • Triage from the attempt's start, not the failure time.

    Rule of thumb: no assertion is signed off until its cover has hit.

    How this was checked

    Run with Verilator 5.048 (--binary --timing --assert, plus --coverage when a listing has a cover property) on 2026-10-18; every bench printed the PASS line shown. Verilator stops at the first $error or failing assertion without an action block, so the benches count failures in the action block. Icarus 13 has no concurrent assertions.

    Related topics

    • Data & Reset Drills

    • Q&A: Operators & Semantics

    Previous

    140 / 259

    Next: Functional Coverage in SystemVerilog

    Up next

    Up nextFunctional Coverage in SystemVerilog

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Handshake Drills
    • Pulse & Stability Drills
    • Ordering & Counting Drills