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
    • Formal versus Simulation
    • Transition Systems, Invariants and the Three Property Roles
    • Reset, Initial State and the First Cycle
    • Cone of Influence
      • What the Cone Is
      • COI Reduction Is Exact
      • Reading Cone Size per Property
      • Cone and Runtime Numbers
    • The Formal Tool Flow, Vendor-Neutral
  • Properties and SVA16
  • 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

←Cone of Influence
  1. Topics
  2. Formal Verification
  3. Formal Foundations
  4. Cone of Influence
  5. Reading Cone Size per Property
On this page

    Part 1 of 10, lesson 14 of 19Part 1 of 10/Formal Foundations/Lesson 14 of 19All levels

    Reading Cone Size per Property

    An empty or tiny cone proves instantly and proves nothing: check the cone, then mutate it.

    Why it matters

    A flag property proved in under a second and the team celebrated; the flag logic had been optimised away a week earlier and the assertion was checking a constant. The proof was instant because there was nothing to check. A one-line cone report would have shown no design flops and stopped the celebration. This lesson makes that report a habit.

    Job of this lesson: show that an assertion with an empty or tiny cone proves instantly and proves nothing, and give the checklist that catches it.

    The core idea

    Read the cone size beside every verdict. A property whose cone holds none of the expected signals is disconnected: an optimised-away output, a wrong path, a constant. It proves instantly because the solver has nothing to work on. The confirmation is a mutation (planting a bug in the logic the property should watch): the property must fail on it. A property that survives the mutation is decorative. The checklist: the cone size matches the expected logic; a mutation inside the expected cone fails the property; a mutation outside it leaves the verdict unchanged.

    Worked example: a pass that proves nothing

    Two kinds of dead wire behave differently. An undriven wire is a free input, so an assertion on it fails at once: a counterexample, not a pass. A constant wire has an empty cone, and an assertion on it proves instantly, on the good design and on the buggy one alike. Below, the buggy FIFO (push accepted while full) carries a constant unused_flag and the assertion !unused_flag:

    sync_fifo.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
    // formal: prove 2 expect pass slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      wire unused_flag = 1'b0;
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_dead: assert (!unused_flag);
    `endif
    endmodule
    sync_fifo.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
    // formal: bmc 12 expect pass slang top sync_fifo define BUG=1
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      wire unused_flag = 1'b0;
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_dead: assert (!unused_flag);
    `endif
    endmodule

    The second run is the lesson: the bug is in the design and the dead assertion passes to depth 12. The partner asserts the same file with a property on a real signal, the occupancy range, and it fails at step 6:

    sync_fifo.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
    // formal: bmc 7 expect fail slang top sync_fifo define BUG=1
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      wire unused_flag = 1'b0;
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_range: assert (count <= 3'd4);
    `endif
    endmodule

    And the undriven wire, which is not an empty cone but a free input:

    sync_fifo.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
    // formal: bmc 4 expect fail slang top sync_fifo define BUG=1
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      wire unused_flag;
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_dead: assert (!unused_flag);
    `endif
    endmodule

    What the review looks for

    • Cone sizes quoted per property, matching the expected logic; no instant pass accepted without its cone report.

    • A mutation inside each cone flips the matching property at a stated step, and one outside leaves it unchanged.

    • Disconnected-wire and constant-wire cases are covered by the checklist, not by luck.

    Verify and debug

    When a property passes faster than its siblings, read its cone first and mutate second. If the cone is empty, trace the signal path: optimisation, wrong hierarchy, or a renamed signal after a refactor. Fix the connection, never the verdict.

    Cone silently empty on a real property. Symptom: an instant proof and a cone with no design flops. Cause: a disconnected assertion: an optimised-away output, a wrong path, a constant. Fix: report cone size per property and mutate a signal in the cone. Found by the cone report and by the mutant that only the real property catches.

    A property too weak to catch its own mutant. Symptom: the assertion passes with an injected bug. Cause: a bound, a guard or a value that excludes the dangerous case. Fix: a mutation set per property (inverted, off by one, a term removed) as an acceptance gate. Found by the surviving mutant.

    “Your property proves in no time. Good news?” Answer: only with the cone report. An empty cone means a disconnected assertion; a tiny cone means it checks less than you think. Show the checklist: cone size, a mutation inside that fails, a mutation outside that passes.

    Check yourself The constant-wire assertion passes on the buggy design and the range assertion fails on it. What does that pair prove about the first?

    That it observes nothing: the design logic is not in its cone, so no bug in the design can reach it.

    Check yourself A property passes instantly. What are the two readings, and which check separates them?

    Trivially true on a small cone, or disconnected on an empty cone. The cone size report separates them, confirmed by a mutation inside the expected cone.

    Related topics

    • Next: Cone and Runtime Numbers

    • Later: Abstraction Techniques

    Previous

    19 / 183

    Next: Cone and Runtime Numbers

    Up next

    Up nextCone and Runtime Numbers

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • What the Cone Is