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
      • |-> vs |=>
      • Vacuous Success
      • Property Operators: not, and, or, if/else, implies
      • nexttime, always, s_eventually, until
      • Designing Good Antecedents
    • Sampled Value Functions
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Implication and Property Operators
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Implication and Property Operators
  5. Vacuous Success

Part 4 · Assertions (SVA) · Intermediate

Vacuous Success

Why passing assertions can check nothing, and the assert-plus-cover discipline that proves antecedents fired.

On this page

    Why this matters

    Ten thousand passes on a grant check meant nothing: the request signal was miswired to a constant, and every pass was vacuous.

    After this lesson you can: spot vacuous success, and pair each assertion with a cover that proves its antecedent fired.

    Passing without checking

    An attempt whose antecedent never matches is a vacuous pass. The obligation never activated, so nothing was violated and nothing was checked.

    The risk is statistical. Thousands of passes can pile up while the consequent logic is never examined once.

    • Slot 0: req 0. Attempt starts, antecedent misses, vacuous pass.

    • Slot 1: req 0. Same outcome, second vacuous pass.

    • Slot 2: req 0. Same outcome, third vacuous pass.

    • Slot 3: req 0. Same outcome, fourth vacuous pass.

    • Slot 4: req 0. Same outcome, fifth vacuous pass.

    • Slot 5: req 0. Same outcome, sixth vacuous pass.

    Six attempts, six vacuous passes, zero examinations of gnt. The tool log looks identical to a healthy run.

    Cover the antecedent

    Every implication ships with a cover of its antecedent sequence. The assert proves never violated, and the cover proves actually exercised.

    Cover the sequence, never the implication. Covering ante |-> cons is itself satisfied by vacuous passes.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet
    a_gnt: assert property (@(posedge clk) disable iff (!rst_n)
      $rose(req) |-> ##[1:2] gnt);
    c_ante: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req));
    c_full: cover property (@(posedge clk) disable iff (!rst_n)
      $rose(req) ##[1:2] gnt);

    Cover hit

    Cover not hit

    Assert passes

    Checked and clean

    Vacuous: fix the stimulus, not the logic

    Assert fails

    Real bug or bad assertion

    Impossible: a nonvacuous failure proves the antecedent fired

    Making vacuity visible

    Simulators separate real and vacuous passes in assertion reports. Consult the vendor docs for the exact options, since flag names differ across tools.

    Keep the sign-off rule in the regression script. Fail the run when an assertion passes but its paired cover has zero hits across the merged regression.

    Common pitfall

    Reading green columns as proof is the classic silent-checker failure. A vacuous-only assertion must block sign-off exactly like a failure.

    Interview

    Likely questions: an assertion passed 10,000 times, is the design verified. Answer: not unless the antecedent fired. Follow with the assert-plus-cover pairing, and why the cover targets the sequence rather than the implication.

    Check yourself Why is cover property (a |-> b) the wrong vacuity guard?

    Vacuous attempts satisfy the implication, so the cover hits without any real activation. Cover the antecedent sequence instead.

    Check yourself Where is the vacuity sign-off rule enforced, and on what data?

    In the regression script, on the merged regression. It fails the run when an assert passes while its paired cover has zero hits.

    Testbench

    A never-firing antecedent plus the paired cover that proves the passes are vacuous. The bench reads a procedural mirror of the cover count, then fires two real requests to show the assertion is live: one granted, one not.

    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
    module tb_impl_vac;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      logic rst_n = 0;
      logic req = 0;
      logic gnt = 0;
      logic req_q = 0;
      int ante_hits = 0;
      int fails = 0;
    
      a_vac: assert property (@(posedge clk) disable iff (!rst_n) req |-> gnt)
        else fails++;
      c_ante: cover property (@(posedge clk) disable iff (!rst_n) $rose(req));
    
      // Procedural mirror of c_ante, so the bench can read the hit count.
      always @(posedge clk) begin
        req_q <= req;
        if (rst_n && req && !req_q) ante_hits <= ante_hits + 1;
      end
    
      initial begin
        int vac_hits, vac_fails;
        repeat (2) @(negedge clk);
        rst_n = 1;
        repeat (6) @(negedge clk);       // phase 1: req never rises
        vac_hits = ante_hits;
        vac_fails = fails;
        req = 1; gnt = 1;                // phase 2: a granted request
        @(negedge clk);
        req = 0; gnt = 0;
        @(negedge clk);
        req = 1;                         // phase 3: a request with no grant
        @(negedge clk);
        req = 0;
        repeat (2) @(negedge clk);
        if (vac_hits == 0 && vac_fails == 0 && ante_hits == 2 && fails == 1)
          $display("PASS: 6 vacuous cycles: 0 cover hits, 0 failures; then 2 real requests: 2 hits, 1 failure");
        else
          $fatal(1, "FAIL: vac_hits=%0d vac_fails=%0d ante_hits=%0d fails=%0d", vac_hits, vac_fails, ante_hits, fails);
        $finish;
      end
    endmodule
    Terminal
    PASS: 6 vacuous cycles: 0 cover hits, 0 failures; then 2 real requests: 2 hits, 1 failure

    Remember

    • A false antecedent passes without checking anything.

    • Cover the antecedent, not the implication.

    • Sign-off fails on an assert whose cover never hit.

    Rule of thumb: no assertion counts until its antecedent 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

    • Designing Good Antecedents

    • Assertion Debug

    Previous

    111 / 259

    Next: Property Operators: not, and, or, if/else, implies

    Up next

    Up nextProperty Operators: not, and, or, if/else, implies

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • nexttime, always, s_eventually, until
    • Designing Good Antecedents
    clkreqgnt
    A request that never rises: six vacuous passes that check nothing.