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
      • Debugging Assertion Failures
      • When Passing Is the Bug: Vacuity & Coverage
      • Assertion Performance & Noise Control
      • Where Assertions Live
      • The Assertion Review Checklist
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Assertion Debug & Methodology
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Assertion Debug & Methodology
  5. When Passing Is the Bug: Vacuity & Coverage

Part 4 · Assertions (SVA) · Intermediate

When Passing Is the Bug: Vacuity & Coverage

Catch vacuous passes with cover properties, cover-first habits, and density review.

On this page

    Why this matters

    A fully green assertion suite shipped a broken arbiter. Half the assertions had never seen their antecedent fire.

    After this lesson you can: audit vacuous passes, pair every assertion with an antecedent cover, and develop checks cover-first.

    Green is not the same as checked

    An implication a |-> b passes vacuously on every attempt where a is false. The consequent was never examined.

    A stimulus that never raises a passes every time while verifying nothing. The usual causes are a typo in the antecedent, a tied-off enable, or a configuration that routes traffic elsewhere.

    Assertion

    Attempts

    Real passes

    Vacuous

    Verdict

    ap_req_ack

    500000

    4982

    495018

    Healthy

    ap_retry_win

    500000

    0

    500000

    Red alert: retry never sampled high

    ap_err_resp

    500000

    0

    500000

    Red alert: never exercised

    Per-assertion real-pass counts come from metrics-enabled regressions, for example commercial assertion dashboards. Default pass or fail summaries do not separate real from vacuous passes, so a zero real-pass count is a red alert dressed in green.

    Common pitfall

    Equating a passing regression with a verified protocol is the expensive mistake. Vacuity statistics belong to sign-off. Deleting quiet covers during cleanup destroys the evidence that asserts still exercise after design changes.

    Check yourself An assertion passes 500000 times with zero real passes. What are the three possible causes?

    Stimulus never generates the scenario, the antecedent is wrong such as a typo, or the scenario is configured off. The pass column alone cannot distinguish them.

    Cover pairs on antecedents and shapes

    The structural fix is a cover property on the antecedent of every implication. Cover hits turn “did this check ever try” into a regression number, and they double as scenario coverage.

    The listing below is a fragment without a module, an illustration the course checks skip; Verilator 5.048 accepts it inside a module. The runnable testbench below it keeps the same idea with counters that sample at the clock edge.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet
    // The assert/cover pair - write them together, always
    ap_req_ack: assert property (@(posedge clk) disable iff (!rst_n)
      $rose(req) |-> ##[1:4] ack);
    
    cp_req: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req));                              // antecedent ever true?
    
    cp_req_ack: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req) ##[1:4] ack);                  // full success shape seen?
    
    // Cover interesting variants, not just the minimum:
    cp_b2b: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req) ##[1:4] ack ##1 $rose(req));   // back-to-back transactions
    cp_ack_min: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req) ##1 ack);                      // shortest latency hit?
    cp_ack_max: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req) ##1 (!ack)[*3] ##1 ack);       // first ack exactly at +4?
    • Cover the antecedent of every implication; zero hits means the assert is decorative.

    • Cover the full success sequence for protocol-critical asserts, not just the trigger.

    • Cover window boundaries too; a ##[1:4] window that only ever hits ##1 has untested timing.

    • Cover properties never fail, they only count, at negligible cost next to their value.

    Cover first and density review

    Cover-first inverts the usual flow. Write the cover for the scenario first, run a smoke simulation, and confirm it hits.

    If it never hits, fix the stimulus or the protocol understanding before writing the assert. A cover that never hits exposes a wrong mental model before a false assert pollutes the regression.

    1. Write the cover property for the scenario first.

    2. Run a quick smoke simulation and confirm the cover hits.

    3. Convert to the assert, keeping the cover alive next to it.

    4. In regression, monitor both: asserts catch violations, covers prove continued exercise.

    Density review asks which protocol rules have no assertion at all. Walk the specification rule list against the assertion inventory, per interface and per state machine.

    Every specification shall sentence should map to an assert, a cover, or a documented waiver. Typical finding: five assertions on the request channel and zero on the response channel.

    Interview

    Two reliable questions: “what is a vacuous pass and why is it dangerous” and “how do you guard against it”. Answer: an implication with a never-true antecedent passes while checking nothing, and the summary stays green. Guard with per-assertion vacuity statistics plus a cover on every antecedent, reviewed at sign-off. Bonus credit for cover-first development.

    Check yourself Your new assertion passes on the first run and has never really passed. What do you do before trusting it?

    Check the antecedent cover count. Then make it fail on purpose, for example by breaking the stimulus, to prove the check can fire. An assert nobody has seen fail or really pass is unproven in both directions.

    Testbench

    One assert with cover counters that sample at the clock edge, the way a cover property does. Scenario 1 never raises retry, scenario 2 rises req during reset, scenario 3 violates the rule, scenario 4 grants in time.

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    module tb_vacuity;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 1'b0;
      always #5 clk = ~clk;
      logic rst_n = 1'b0;
      logic req = 1'b0;
      logic retry = 1'b0;
      logic gnt = 1'b0;
      logic req_prev = 1'b0;
      int unsigned ante_hits = 0;   // cover: $rose(retry) sampled at the edge
      int unsigned ok_hits = 0;     // cover: $rose(retry) ##[1:8] gnt
      int unsigned rose_post_reset = 0;
      int unsigned fail_cnt = 0;
      int unsigned bad = 0;
    
      ap_retry_win: assert property (@(posedge clk) disable iff (!rst_n)
        $rose(retry) |-> ##[1:8] gnt)
        else begin
          fail_cnt++;
          $display("caught retry without grant at %0t", $time);
        end
    
      // Cover counters observe the signals at the clock edge, out of reset,
      // like the cover properties above. They never touch the stimulus.
      logic retry_q = 1'b0;
      int win = 0;                  // cycles left in the open ##[1:8] window
      always @(posedge clk) begin
        if (!rst_n) win = 0;
        else begin
          if (gnt && win > 0) ok_hits++;
          if (win > 0) win--;
          if (retry && !retry_q) begin ante_hits++; win = 8; end
        end
        retry_q <= retry;
      end
    
      initial begin
        @(negedge clk); @(negedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);
        // Scenario 1: ten request pulses, retry never raised; the assert is vacuous.
        repeat (10) begin
          @(negedge clk); req = 1'b1;
          @(negedge clk); req = 1'b0;
        end
        if (ante_hits != 0) bad++;
        // Scenario 2: req rises during reset and stays high past release.
        @(negedge clk);
        rst_n = 1'b0; req = 1'b1;
        repeat (2) @(posedge clk);
        @(negedge clk); rst_n = 1'b1;
        req_prev = req;                         // req already high: settle tracker at 1
        repeat (8) begin                        // eight quiet cycles, still no rise
          @(posedge clk); #1;
          if (req && !req_prev) rose_post_reset++;
          req_prev = req;
        end
        if (rose_post_reset != 0) bad++;
        // Fix the stimulus: drop req, then pulse it once after reset.
        @(negedge clk); req = 1'b0;
        @(posedge clk); #1; req_prev = req;     // settle the tracker at 0
        @(negedge clk); req = 1'b1;
        @(posedge clk); #1;
        if (req && !req_prev) rose_post_reset++;
        req_prev = req;
        @(negedge clk); req = 1'b0;
        if (rose_post_reset != 1) bad++;
        // Scenario 3: one retry with no grant inside eight cycles; the assert fires.
        @(negedge clk); retry = 1'b1;
        @(posedge clk);
        @(negedge clk); retry = 1'b0;
        repeat (8) @(posedge clk);
        @(negedge clk);                       // let the fired action settle before counting
        if (fail_cnt != 1) bad++;
        // Scenario 4: retry followed by a grant two cycles later; no new failure.
        @(negedge clk); retry = 1'b1;
        @(posedge clk);
        @(negedge clk); retry = 1'b0;
        repeat (2) @(posedge clk);
        @(negedge clk); gnt = 1'b1;
        @(posedge clk); #1;
        @(negedge clk); gnt = 1'b0;
        repeat (8) @(posedge clk);               // let the window close quietly
        if (fail_cnt != 1) bad++;               // still one: scenario 4 must not fire
        if (ante_hits != 2) bad++;
        if (ok_hits != 1) bad++;
        if (bad == 0) $display("PASS: vacuous scenarios counted zero and the real violation was caught");
        else $fatal(1, "FAIL: vacuity testbench");
        $finish;
      end
    endmodule
    Terminal
    caught retry without grant at 455000
    PASS: vacuous scenarios counted zero and the real violation was caught

    Remember

    • A pass with a false antecedent checked nothing.

    • Cover the antecedent, and require the cover to hit.

    • Write the cover before trusting the assert.

    Rule of thumb: an assertion whose antecedent cover never hit blocks sign-off.

    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

    • Debugging Assertion Failures

    Previous

    129 / 259

    Next: Assertion Performance & Noise Control

    Up next

    Up nextAssertion Performance & Noise Control

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Where Assertions Live
    • The Assertion Review Checklist