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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Glossary
  • Metrics Reference
  • Formal Cheat Sheet
  • Formal Foundations19
  • Properties and SVA16
    • SVA as a Formal Tool Reads It
    • Implication, Edges and Windows
    • Safety, Liveness and What a Tool Can Say
    • Debugging and Reviewing Assertions
      • Reading a Failing Assertion in a Trace
      • Vacuous and Trivially True Assertions
      • Mutation Check of a Property
      • Property Review Checklist
  • Property Development and Constraints19
  • Proof Engines and Convergence23
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Debugging and Reviewing Assertions
  1. Topics
  2. Formal Verification
  3. Properties and SVA
  4. Debugging and Reviewing Assertions
  5. Vacuous and Trivially True Assertions
On this page

    Part 2 of 10, lesson 14 of 16Part 2 of 10/Properties and SVA/Lesson 14 of 16All levels

    Vacuous and Trivially True Assertions

    An unreachable trigger passes every check and needs a cover to expose it; a bound the value cannot exceed passes every mutant and needs a mutation to expose it.

    Why it matters

    Two passing results in the report prove nothing. The first is vacuity: the trigger never fires, so the rule is never tested. The second is the tautology: the rule is true of every value, so no design can break it. Both pass on the mutant that should kill them. This lesson's one job is the two different exposures: a cover for the trigger, a mutant for the tautology.

    The core idea

    Vacuity means the antecedent is never true: a state the machine never reaches, an encoding the decoder never emits, an enable that never rises. The consequent can be anything, including false, and the rule still passes. The exposure is an antecedent cover: reach the trigger, or admit the rule was never tried. A weak sequence that never completes is not vacuity, it is an incomplete attempt with its own window reasoning; do not confuse the two.

    Read an unreached cover carefully. A cover not reached within a depth is inconclusive: a longer run might still reach it. It becomes a statement of unreachability only when something else shows the state cannot be reached, for example a proof of an invariant that excludes it. The worked example below does both: the cover is not reached, which is inconclusive on its own, and a one-line invariant is then proved, which turns the suspicion into a fact.

    A tautology is true by construction: a three-bit value never exceeds seven, a one-hot check on a signal the encoder guarantees, a bound the type already enforces. The exposure is mutation: break the design in a way the rule should catch, and watch the rule pass anyway. Covers cannot see tautologies, because the trigger fires constantly; only a mutant the rule misses proves it checks nothing.

    Worked example: a trigger in a state that does not exist

    The machine below cycles through eight states, 0 to 7, and the rule demands something of state nine, which never happens. The property passes, and the cover of the trigger is not reached:

    fsm_trig.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // formal: bmc 8 expect pass slang
    `define FORMAL
    // fsm_trig: an eight-state machine (states 0 to 7) with a rule about state 9, which never
    // happens. a_state9 passes vacuously and c_state9 is its antecedent cover. a_range, st <= 7, is
    // the invariant that turns the unreached cover into a proof that state 9 is unreachable.
    module fsm_trig (
      input  wire clk,
      input  wire rst
    );
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      reg [3:0] st;
      always @(posedge clk) begin
        if (rst) st <= 4'd0;
        else if (st == 4'd7) st <= 4'd0;
        else st <= st + 4'd1;
      end
      wire out_ok = 1'b0;
      always @(*) if (!init) a_state9: assert (!(st == 4'd9) || out_ok);
      always @(*) if (!init) a_range: assert (st <= 4'd7);
      always @(*) if (!init) c_state9: cover (st == 4'd9);
    endmodule
    fsm_trig.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // formal: cover 8 expect fail slang
    `define FORMAL
    // fsm_trig: an eight-state machine (states 0 to 7) with a rule about state 9, which never
    // happens. a_state9 passes vacuously and c_state9 is its antecedent cover. a_range, st <= 7, is
    // the invariant that turns the unreached cover into a proof that state 9 is unreachable.
    module fsm_trig (
      input  wire clk,
      input  wire rst
    );
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      reg [3:0] st;
      always @(posedge clk) begin
        if (rst) st <= 4'd0;
        else if (st == 4'd7) st <= 4'd0;
        else st <= st + 4'd1;
      end
      wire out_ok = 1'b0;
      always @(*) if (!init) a_state9: assert (!(st == 4'd9) || out_ok);
      always @(*) if (!init) a_range: assert (st <= 4'd7);
      always @(*) if (!init) c_state9: cover (st == 4'd9);
    endmodule

    Not reached within 8 is inconclusive by itself. Here the reason is the counter, which wraps from 7 to 0. The invariant that the state never exceeds 7 is proved with its base case and its induction step, and it excludes state nine for good, so the unreachability is now a proof and not a hunch:

    fsm_trig.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // formal: prove 4 expect pass slang
    `define FORMAL
    // fsm_trig: an eight-state machine (states 0 to 7) with a rule about state 9, which never
    // happens. a_state9 passes vacuously and c_state9 is its antecedent cover. a_range, st <= 7, is
    // the invariant that turns the unreached cover into a proof that state 9 is unreachable.
    module fsm_trig (
      input  wire clk,
      input  wire rst
    );
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      reg [3:0] st;
      always @(posedge clk) begin
        if (rst) st <= 4'd0;
        else if (st == 4'd7) st <= 4'd0;
        else st <= st + 4'd1;
      end
      wire out_ok = 1'b0;
      always @(*) if (!init) a_state9: assert (!(st == 4'd9) || out_ok);
      always @(*) if (!init) a_range: assert (st <= 4'd7);
      always @(*) if (!init) c_state9: cover (st == 4'd9);
    endmodule

    Worked example: a bound no value can break

    The rule below checks a three-bit value against seven. It passes on the good mirror and on the mutant that inverts every bit, because no three-bit value exceeds seven either way. The real check beside it, output equals input, tells the two apart:

    taut_check.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // formal: bmc 4 expect pass slang
    `define FORMAL
    // taut_check: out mirrors v, or its inverse under BUG=1. p_taut is true by
    // construction on both; p_real tells the good design from the mutant.
    module taut_check #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [2:0] v,
      output wire [2:0] out
    );
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      assign out = (BUG == 1) ? ~v : v;
      always @(*) if (!init) begin
        p_taut: assert (v <= 3'd7);
        p_real: assert (out == v);
      end
    endmodule
    taut_check.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // formal: bmc 4 expect fail slang define BUG=1
    `define FORMAL
    // taut_check: out mirrors v, or its inverse under BUG=1. p_taut is true by
    // construction on both; p_real tells the good design from the mutant.
    module taut_check #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [2:0] v,
      output wire [2:0] out
    );
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      assign out = (BUG == 1) ? ~v : v;
      always @(*) if (!init) begin
        p_taut: assert (v <= 3'd7);
        p_real: assert (out == v);
      end
    endmodule

    In the second run the failing label is the real check; the tautology still passes in the same run. A suite made only of tautologies would call this mutant a pass.

    What the review looks for

    • Every implication-style rule has an antecedent cover, and the report shows it reached: an unreached trigger fails sign-off, not just the rule.

    • An unreached cover is called inconclusive until a proof or a design argument shows the state cannot happen; only then is it called unreachable.

    • Every bound is checked against the type width: a comparison the width already decides is a tautology until a mutant proves otherwise.

    • Weak sequences and vacuity are named separately: an incomplete attempt gets window reasoning, an unfired trigger gets a cover.

    Verify and debug

    Run the cover with the proof, every time, not after it. A passing proof with an unreached antecedent cover is the definition of a vacuous result: fix the trigger or delete the rule, never ship the pass. For suspected tautologies, mutate first and read second: if no mutant in the rule's cone fails it, the rule checks nothing, however often it passes.

    Unreachable antecedent signed off as proven: the rule guarded on a dead encoding passed every regression, and the cone it guarded changed silently underneath it. Found by the antecedent cover, which had never been reached in any run. Fixed by deleting the dead rule and writing the rule the live encoding actually needs, with its cover reached from the first run.

    How do you tell vacuity from a tautology? Answer: vacuity is a trigger that never fires, exposed by an unreached antecedent cover; a tautology fires constantly and is true by construction, exposed only by a mutant it cannot catch. Add that an unreached cover is inconclusive until something proves the state unreachable. Evidence: the state-nine rule passes its proof, its cover is not reached, and a proved range invariant explains why; the seven-bound passes both its proof and its mutant.

    Check yourself The cover reaches but the proof fails on the mutant. Is the rule vacuous?

    No: the cover proves the trigger fires, so the rule was tried. It fails because the design is wrong or the rule is, which the trace then decides. Vacuity is only the case where the trigger never fires at all.

    Related topics

    • Next: Mutation Check of a Property

    • SystemVerilog: Vacuous Passes

    • SystemVerilog: Debugging Passes

    • Formal: Cover Properties

    Previous

    38 / 183

    Next: Mutation Check of a Property

    Up next

    Up nextMutation Check of a Property

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Property Review Checklist