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
  • 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
    • The Brief
    • Stage 1: FIFO and Handshake
    • Stage 2: Registers and Arbiter
    • Stage 3: Pipeline and Equivalence
    • Stage 4: Induction and Signoff
    • Solutions
      • Solutions: FIFO Property Set
      • Solutions: Bridge and the Assumption Trap
      • Solutions: Registers and the Property Mutants
      • Solutions: Arbiter Wait Bound
      • Solutions: Pairs and Chain
      • Solutions: Pipeline, Twin and ECO
      • Solutions: Counter and Induction
      • Solutions: Ledger and Final Manifest
    • Appendix: Run It Yourself on an Open-Source Flow
Loading lesson

←Solutions
  1. Topics
  2. Formal Verification
  3. Capstone: A Chip-Block Verification Lab
  4. Solutions
  5. Solutions: FIFO Property Set
On this page

    Part 10 of 10, lesson 15 of 24Part 10 of 10/Capstone: A Chip-Block Verification Lab/Lesson 15 of 24All levels

    Solutions: FIFO Property Set

    The cause, killing property, step and fix of the five FIFO faults, the survivors of a reduced set, and the mutants of the properties no fault reaches.

    Why it matters

    A property set is evidence only after each property has failed on something. The five FIFO faults reach most of the set; the rest needs mutants. This lesson gives the causes and steps, shows what a bound one depth short would have said, and shows which fault survives when the data property is removed.

    The five faults

    Variant

    Cause in the listing

    Fix

    Fails (step), smallest depth

    W1

    The push gate misses the full term: pushes are accepted when full.

    Gate pushes on not-full.

    a_range (6), then a_ovf_ign (7); depth 7

    W2

    The pop gate misses the empty term: pops are accepted when empty.

    Gate pops on not-empty.

    a_range (2); depth 3

    W3

    The full flag is count >= 3.

    full = (count == 4).

    a_flags (4); depth 5

    W4

    With a simultaneous push and pop the count adds one and forgets the pop.

    count + do_push - do_pop.

    a_ptr (3), a_both (4); depth 4; a_range never fails to depth 14

    W5

    Write data bit 7 is masked to 0.

    Write wdata unchanged.

    a_data (2), a_tracked (2); depth 3; no count property fails

    Step 0 is the reset; a combinational property reports the step where the bad state exists, a clocked one a step later. A counterexample at step N needs depth N + 1.

    Worked example: W1, read step by step

    Step

    push

    count

    What happens

    0

    -

    arbitrary

    Power-up; the harness asserts reset.

    1

    1

    0

    First state after reset.

    2 to 4

    1

    1, 2, 3

    Filling, one word per cycle.

    5

    1

    4

    Full: the fifth push must be ignored.

    6

    -

    5

    The faulty gate accepted it: a_range fails here.

    The pair that matters is depth 6 and depth 7: the first passes on the faulty design, the second fails.

    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
    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
    92
    93
    94
    // formal: bmc 6 expect pass slang top sync_fifo define BUG=1
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    // formal: bmc 7 expect fail slang top sync_fifo define BUG=1
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    W2 needs one pop after reset; W3 needs three pushes.

    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
    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
    92
    93
    94
    // formal: bmc 3 expect fail slang top sync_fifo define BUG=2
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    // formal: bmc 5 expect fail slang top sync_fifo define BUG=3
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    W4 is the lesson in internal consistency: the count drifts away from the pointers at step 3, a_ptr sees it, and a set made only of the range property never sees it, even to depth 14. The first block below is the faulty design with every assertion but a_range turned into a cover; the second is the full set.

    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
    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
    92
    93
    94
    95
    // formal: bmc 14 expect pass slang top sync_fifo define BUG=4
    `define FORMAL
    // property-set mutation: every assertion except a_range is turned into a cover (its label starts with x_)
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        x_a_flags:   cover (full == (count == 3'd4) && empty == (count == 3'd0));
        x_a_not_both: cover (!(full && empty));
        x_a_ptr:     cover (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        x_a_push_inc: cover (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        x_a_pop_dec:  cover (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        x_a_ovf_ign:  cover (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        x_a_both:     cover (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) x_a_data: cover (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) x_a_tracked: cover (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    // formal: bmc 4 expect fail slang top sync_fifo define BUG=4
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    W5 corrupts a value and leaves every count right. The first block runs the control set only (counts, flags, pointers, transitions) and passes; the second adds the data property and fails at depth 3.

    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
    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
    92
    93
    94
    95
    // formal: bmc 12 expect pass slang top sync_fifo define BUG=5
    `define FORMAL
    // property-set mutation: every assertion except the count, flag, pointer and transition properties is turned into a cover (its label starts with x_)
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) x_a_data: cover (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) x_a_tracked: cover (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    // formal: bmc 3 expect fail slang top sync_fifo define BUG=5
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    The induction window

    The four transition properties read one cycle of history, so a window of one cannot close them and a window of two can.

    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
    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
    92
    93
    94
    // formal: prove 1 expect unproven slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    // formal: prove 2 expect pass slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    Without the helper assertion of the tracked slot the data property cannot close its step at any window to 12, and the honest filing is inconclusive, with a bounded run beside it: a bounded pass to depth 20, past the farthest control state at step 8, which is more than the minimum of 9 and still not a proof.

    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
    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
    92
    93
    94
    // formal: bmc 20 expect pass slang top sync_fifo define HELPER=0
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    Mutants for the properties no fault reaches

    Three FIFO properties fail on none of the five faults: a_not_both, a_push_inc and a_pop_dec. Each gets a one-line mutant, killed by its property; the full set kills fifo-m-not-both also through a_flags.

    Mutant

    Change

    Killer

    Smallest depth

    fifo-m-not-both

    full is also true when the FIFO is empty

    a_not_both

    2

    fifo-m-push-inc

    a lone push at occupancy 2 advances the write pointer and the count by two

    a_push_inc

    6

    fifo-m-pop-dec

    a lone pop at occupancy 3 advances the read pointer and drops the count by two

    a_pop_dec

    7

    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
    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
    92
    93
    94
    95
    // formal: bmc 2 expect fail slang top sync_fifo
    `define FORMAL
    // mutant fifo-m-not-both: full is also true when the FIFO is empty
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4) || (count == 3'd0);
      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;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    95
    // formal: bmc 6 expect fail slang top sync_fifo
    `define FORMAL
    // mutant fifo-m-push-inc: a lone push at occupancy 2 advances the write pointer and the count by two
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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 + ((!do_pop && count == 3'd2) ? 2'd2 : 2'd1);
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop} + ((do_push && !do_pop && count == 3'd2) ? 3'd1 : 3'd0);
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `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
    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
    92
    93
    94
    95
    // formal: bmc 7 expect fail slang top sync_fifo
    `define FORMAL
    // mutant fifo-m-pop-dec: a lone pop at occupancy 3 advances the read pointer and drops the count by two
    // sync_fifo: 4-entry, 8-bit synchronous FIFO with a combinational read of the oldest word.
    // Synchronous active-high reset. count is an output so that properties can see it.
    // BUG selects a variant (0 is the design as specified); HELPER=0 removes the helper assertion.
    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
    );
      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 + ((!do_push && count == 3'd3) ? 2'd2 : 2'd1);
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop} - ((do_pop && !do_push && count == 3'd3) ? 3'd1 : 3'd0);
        end
      end
    
    `ifdef FORMAL
      // ---- harness: the first cycle is a reset, the state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (they read history, so skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction: one free slot, one shadow value ----
      // The free constant is an anyconst wire that is also assumed equal to its one-cycle-old copy;
      // this form works in both front ends, while a clocked assume of stability lags one step.
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a word we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the triggers of the properties can happen ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
        c_tracked_read: cover (tv && !empty && rptr == tslot);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule
    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # property -> {variant: failing step}, typed from the runs of this topic and of the bridge lessons
    KILLS = {"a_range": {"fifo-W1": 6, "fifo-W2": 2}, "a_ovf_ign": {"fifo-W1": 7}, "a_flags": {"fifo-W3": 4},
             "a_ptr": {"fifo-W4": 3}, "a_both": {"fifo-W4": 4},
             "a_data": {"fifo-W5": 2, "bridge-W1": 4, "bridge-W2": 4, "bridge-W3": 3}, "a_tracked": {"fifo-W5": 2},
             "a_conserve": {"bridge-W1": 3, "bridge-W2": 3}, "a_skid_needs_out": {"bridge-W2": 4},
             "a_valid_hold": {"bridge-W2": 4}, "a_data_hold": {"bridge-W3": 4}, "h_in_out": {"bridge-W2": 3, "bridge-W3": 3}}
    PLANTED = ["fifo-W%d" % i for i in range(1, 6)] + ["bridge-W%d" % i for i in range(1, 4)]
    def survivors(removed):
        live = set(PLANTED)
        for prop, hits in KILLS.items():
            if prop not in removed:
                live -= set(hits)
        return sorted(live)
    def score(removed):
        return (len(PLANTED) - len(survivors(removed))) / len(PLANTED)
    assert survivors(set()) == []
    assert survivors({"a_data", "a_tracked"}) == ["fifo-W5"], "the bridge fault that corrupts a value is still seen by the hold property"
    assert score({"a_data", "a_tracked"}) == 0.875
    assert survivors({"a_data", "a_tracked", "a_data_hold", "h_in_out"}) == ["bridge-W3", "fifo-W5"]
    assert score({"a_data", "a_tracked", "a_data_hold", "h_in_out"}) == 0.75
    first = {v: min(s for hits in KILLS.values() for w, s in hits.items() if w == v) for v in PLANTED}
    assert first == {"fifo-W1": 6, "fifo-W2": 2, "fifo-W3": 4, "fifo-W4": 3, "fifo-W5": 2, "bridge-W1": 3, "bridge-W2": 3, "bridge-W3": 3}
    assert {v: s + 1 for v, s in first.items()}["fifo-W1"] == 7, "a counterexample at step 6 needs depth 7"
    print("PASS: removing the data properties leaves exactly fifo-W5 alive (7 of 8 killed); removing every value check leaves two")

    What the review looks for

    • Each fault has a property, a step and a smallest depth, shown by a run and its shorter passing partner where one matters.

    • No property of the set is untested: the three mutants give a_not_both, a_push_inc and a_pop_dec a failure of their own.

    • The survivor under a reduced set names the missing property: here, data integrity.

    Verify and debug

    If your failing step differs from the table, check which assertion style you counted: combinational properties report the step of the bad state, clocked ones one step later. If a mutant of yours survives, check that the property you expect reads the signal you changed.

    Bounded pass filed as clean: the run stopped one depth before the evidence. Symptom is a faulty design with a clean result at depth 6. Cause is a depth chosen without a cover step behind it; the ledger minimum for this FIFO is 9, from the farthest control state at step 8. Fix: file the depth with its justification and prefer the induction proof. Found by rerunning one depth deeper.

    “How do you know your FIFO property set is complete?” Answer: every property has failed on a fault or a mutant, the survivors of each reduced set name the property that was removed, and the induction window is measured, not assumed. Quote the W4 and W5 pair: a count-only set is blind to both.

    Check yourself Why does W4 pass a set that contains only the range property?

    The range property looks at the count alone, and W4 keeps the count in range for a long time while it drifts from the pointers. Only the internal consistency property sees the drift early.

    Check yourself Why is the mutation score 7 of 8 and not 6 of 8 when the data properties are removed?

    The bridge fault that corrupts a value is also noticed by the hold property, so only the FIFO fault, which no other property sees, survives.

    Related topics

    • Back: Stage 1 FIFO Property Set

    • Next: Solutions, Bridge and the Assumption Trap

    • Earlier: Mutation-Based Formal Coverage

    Previous

    174 / 183

    Next: Solutions: Bridge and the Assumption Trap

    Up next

    Up nextSolutions: Bridge and the Assumption Trap

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Solutions: Registers and the Property Mutants
    • Solutions: Arbiter Wait Bound
    • Solutions: Pairs and Chain