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
    • Writing Properties That Can Fail and Say Something
      • Spec Sentence to Property
      • End to End Versus Local Properties
      • Symbolic Constants and Tracked Items
      • Widths, Signedness and Initial State
    • Assumptions as a Contract
    • Abstraction and Complexity
    • Assume-Guarantee and Black Boxes
    • Reusable Property Sets
  • 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

←Writing Properties That Can Fail and Say Something
  1. Topics
  2. Formal Verification
  3. Property Development and Constraints
  4. Writing Properties That Can Fail and Say Something
  5. End to End Versus Local Properties
On this page

    Part 3 of 10, lesson 2 of 19Part 3 of 10/Property Development and Constraints/Lesson 2 of 19All levels

    End to End Versus Local Properties

    Layer one end-to-end property over local stage relations, and see which one catches a bug and which one names the stage.

    Why it matters

    A pipeline bug can sit between two stages that each look fine. The three-stage pipeline gives y = ((a + b)[3:0] * c[3:0]) + 1 three cycles after the inputs; its retimed twin moves the multiplier one stage later. Job of this lesson: show what an end-to-end property catches without naming a stage, what a local relation names and misses, and how the two are layered.

    The core idea

    The end-to-end property compares the output with the specified function of inputs three cycles old. It sees every functional fault and points only at the output. A local relation ties one stage boundary together: it names the stage and sees only what its relation covers. Write the end-to-end property first; add local relations where a failure must be traced, and prove them, never assume them.

    Top-down diagram: saved inputs feed two checks. The end-to-end check leads to a failure that says the output is wrong and the stage is unknown. The local relation leads to a failure that names its own stage.
    Both layers start from the same saved inputs. The end-to-end check reads only the output, so its failure does not name a stage; the local relation reads one stage boundary, so its failure does.

    Layer

    Catches

    Localises

    Cost

    End to end (a_latency3, a_equiv)

    Any functional fault on the path

    Only to the output

    Needs the input history; induction at k = 3

    Local relation (a_loc3)

    Faults inside its own stage

    To that stage

    One relation per stage, each proved

    Internal invariant (h_cut)

    A broken cut between registers

    To the cut

    Strengthens the induction to k = 2

    Worked example

    The reference pipeline proves its end-to-end property. A lesson-local copy takes a BUG parameter with two real faults: stage 2 takes its operand straight from the input instead of the stage-1 register, and stage 3 skips stage 2. Both are caught by a_latency3 at step 4; depth 4 passes.

    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    // formal: prove 4 expect pass slang top pipe3
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_equiv: assert (same);
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule
    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    // formal: bmc 5 expect fail slang top pipe3 define BUG=1
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1, parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (BUG == 1) ? s[3:0] * c[3:0] : s[3:0] * c1;      // BUG 1: stage 2 takes its operand straight from the input
          y  <= (BUG == 2) ? s[3:0] * c1 + 8'd1 : p + 8'd1;      // BUG 2: stage 3 skips stage 2
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_equiv: assert (same);
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule
    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    // formal: bmc 5 expect fail slang top pipe3 define BUG=2
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1, parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (BUG == 1) ? s[3:0] * c[3:0] : s[3:0] * c1;      // BUG 1: stage 2 takes its operand straight from the input
          y  <= (BUG == 2) ? s[3:0] * c1 + 8'd1 : p + 8'd1;      // BUG 2: stage 3 skips stage 2
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_equiv: assert (same);
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule

    Step

    Inputs

    Reference

    What breaks

    1

    a 0, b 1, c 1 (for example)

    -

    Vector enters stage 1

    2

    0, 0, 0

    -

    BUG 1: stage 2 multiplies by the new input 0, not by c1 = 1

    4

    -

    y should be 2

    BUG 1: y is 1, so a_latency3 fails

    The retimed twin has a different fault: its multiplier drops bit 3 of the operand (not a swapped stage). The miter compares the twins end to end and fails at step 4 without naming a stage:

    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    // formal: bmc 5 expect fail slang top pipe3_eq define BUG=1
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_equiv: assert (same);
      always @(posedge clk) if (!init && !$past(rst)) a_loc3: assert (y2 == $past(s2_ret) * $past(c2_ret) + 8'd1);   // stage 3 on its own
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule

    The solver picks any vector with bit 3 of c set and (a + b)[3:0] non-zero; for example a 8, b 0, c 8 at step 1 gives 65 from the reference and 1 from the twin at step 4. Now only the local relation of stage 3 (a_loc3: the twin's output equals the product of its own stage-2 registers, plus 1) is checked, with the end-to-end check turned into a cover. It fails at step 5, depth 6, and names the stage. The cut invariant h_cut alone does not see this fault:

    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    // formal: bmc 6 expect fail slang top pipe3_eq define BUG=1
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) c_differ: cover (!same);   // the end-to-end check is off; this only watches
      always @(posedge clk) if (!init && !$past(rst)) a_loc3: assert (y2 == $past(s2_ret) * $past(c2_ret) + 8'd1);   // stage 3 on its own
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule
    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    // formal: bmc 12 expect pass slang top pipe3_eq define BUG=1
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) c_differ: cover (!same);   // the end-to-end check is off; this only watches
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule
    pipe3.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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    // formal: prove 5 expect pass slang top pipe3_eq
    `define FORMAL
    // pipe3: three-stage arithmetic pipeline, y = ((a + b)[3:0] * c[3:0]) + 1 (8 bits),
    // registers: stage 1 {s = a + b, c1 = c[3:0]}, stage 2 {p = s[3:0] * c1}, stage 3 {y = p + 1}.
    // Latency 3 cycles (y at cycle t+3 uses a, b, c of cycle t). Synchronous active-high
    // reset clears every register. Only a[3:0], b[3:0], c[3:0] influence y.
    // pipe3_retimed: same function and latency, registers moved across the multiplier:
    // stage 1 {s, c1} (same), stage 2 {s2 = s[3:0], c2 = c1} (the multiplier moved to
    // stage 3), stage 3 {y = s2 * c2 + 1}. BUG=1 injects a bug in the retimed twin: the
    // multiplier ignores bit 3 of c2.
    // pipe3_eq: miter of pipe3 against pipe3_retimed with common inputs; BUG is passed to the twin.
    module pipe3 #(parameter CHECK = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p          // stage-2 register, exposed for the miter's helper invariant
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
    `ifdef FORMAL
      // CHECK=0 switches the properties off (used when pipe3 sits inside the miter)
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // hist counts clean cycles since reset (saturates at 3): y is only specified after 3
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      // input history, as plain registers (works in every front end)
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;       // low 4 bits of a + b three cycles ago
    
      // functional property with latency 3, checked only after 3 clean cycles
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      // after reset the output register is 0
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule
    
    module pipe3_retimed #(parameter BUG = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [3:0] dbg_s2,        // stage-2 registers, exposed for the miter's helper invariant
      output wire [3:0] dbg_c2
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [3:0] s2, c2;
      assign dbg_s2 = s2;
      assign dbg_c2 = c2;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; s2 <= 4'd0; c2 <= 4'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          s2 <= s[3:0];
          c2 <= c1;
          y  <= s2 * ((BUG == 1) ? {1'b0, c2[2:0]} : c2) + 8'd1;
        end
      end
    endmodule
    
    module pipe3_eq #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output wire       same
    );
      wire [7:0] y1, y2, p_ref;
      wire [3:0] s2_ret, c2_ret;
      pipe3 #(.CHECK(0))     u_ref (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y1), .dbg_p(p_ref));
      pipe3_retimed #(.BUG(BUG)) u_ret (.clk(clk), .rst(rst), .a(a), .b(b), .c(c), .y(y2), .dbg_s2(s2_ret), .dbg_c2(c2_ret));
      assign same = (y1 == y2);
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      always @(*) if (!init) a_equiv: assert (same);
      always @(posedge clk) if (!init && !$past(rst)) a_loc3: assert (y2 == $past(s2_ret) * $past(c2_ret) + 8'd1);   // stage 3 on its own
      // helper invariant across the moved cut: the reference's stage-2 register p equals the
      // twin's s2 * c2 (the multiplier result computed one stage later). Not needed if k >= 2.
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) h_cut: assert (p_ref == s2_ret * c2_ret);
      end endgenerate
      always @(*) if (!init) c_nonzero: cover (y1 == 8'd7 && y2 == 8'd7);
    `endif
    endmodule

    The last run proves the local relation together with the end-to-end check on the good twin, to depth 5. The cut passes to depth 12 on the buggy twin because the relation p == s2 * c2 links stage 2 of the reference to stage 2 of the twin; the dropped bit sits after it.

    What a reviewer looks for

    • The end-to-end property is computed from saved inputs, not from the pipeline registers, so it cannot agree with a broken stage.

    • Every local relation is itself proved; a local relation that is assumed is an untested promise.

    • Each stage has a mutant, and the review notes which property catches it first and at which step.

    Verify and debug

    When the end-to-end property fails, read the trace for the first stage register that disagrees with the saved inputs. When it passes but you suspect a hole, mutate one stage at a time; a mutant that survives needs a local relation at that stage.

    The self-referential check. Symptom: the proof passes and no mutant fails it. Cause: the expected value was written from the implementation expression, here the stage registers, instead of the specification. Fix: replay saved inputs through the specified arithmetic. Found by: mutation, which a self-referential property always survives.

    "What does end-to-end catch that local misses, and the reverse?" End to end sees any fault on the path and names none; local names its stage and misses faults outside its relation. Show the dropped bit: step 4 for the first, step 5 and the stage for the second.

    Check yourself Both mutants of the reference pipeline fail at step 4 and depth 4 passes. Why not earlier?

    The vector enters at step 1 and needs three register stages to reach y at step 4. A bounded run to depth 4 checks steps 0 to 3, which never see the output the vector reaches.

    Related topics

    • Previous: Spec Sentence to Property

    • Strengthening with Helper Invariants

    • Next: Symbolic Constants and Tracked Items

    Previous

    42 / 183

    Next: Symbolic Constants and Tracked Items

    Up next

    Up nextSymbolic Constants and Tracked Items

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Widths, Signedness and Initial State