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
    • Assumptions as a Contract
      • Assumption Taxonomy and Ownership
      • Assume Versus Assert by Side
      • The Assumption Review
      • Turning Assumptions into Assertions
    • 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

←Assumptions as a Contract
  1. Topics
  2. Formal Verification
  3. Property Development and Constraints
  4. Assumptions as a Contract
  5. The Assumption Review
On this page

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

    The Assumption Review

    Run the review on a six-assumption harness: ledger against the harness text, covers, a mutation that survives, and the share of input space removed.

    Why it matters

    A register block proved in seconds under six assumptions, and a planted fault in its read-only register passed all of them. Two assumptions were wrong. Job of this lesson: the review procedure (list, classify, find the discharge, probe over-constraint, measure the share removed), run on that harness.

    The core idea

    The register block sits behind an environment with six assumptions. Four are sound or harmless; two are wrong in different ways.

    #

    Assumption

    Verdict

    1

    The first cycle is a reset

    Sound: reset and initial state

    2

    No register write during reset

    Sound: interface rule, owed by the bus master

    3

    No hardware set pulse during reset

    Sound: interface rule, owed by the event source

    4

    addr <= 3 on a two-bit address

    Harmless but empty: it removes nothing, so delete it

    5

    Nobody writes the read-only ID register

    Wrong: a legal write is removed, and the fault that lets it through is hidden

    6

    The ID register always reads 0xA5

    Wrong: an assumption on a design output prunes the design

    Worked example: the harness runs

    With assumptions 1 to 4 the block proves, and with the ID-write fault (BUG=2) the property a_id_const fails at step 2 (depth 3; depth 2 passes). Add assumption 5 or 6 and the same fault passes to depth 8:

    csr_block.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
    122
    123
    124
    125
    126
    // formal: prove 4 expect pass slang top csr_review
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule
    csr_block.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
    122
    123
    124
    125
    126
    // formal: bmc 3 expect fail slang top csr_review define BUG=2
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule
    csr_block.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
    122
    123
    124
    125
    126
    // formal: bmc 8 expect pass slang top csr_review define BUG=2 define NOID=1
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule
    csr_block.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
    122
    123
    124
    125
    126
    // formal: bmc 8 expect pass slang top csr_review define BUG=2 define OUTA=1
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule

    The passes at depth 8 are the false confidence the review exists to remove. Two probes tell the two wrong assumptions apart. The activity cover for a write to the ID register is reached with assumptions 1 to 4 and not reached within depth 6 with assumption 5: unreached means inconclusive in general, but here the cause is the assumption you can read. With assumption 6 the cover is still reached, because writes of 0xA5 remain legal: the cover does not expose an assumption on an output. The ledger rule and the mutation do.

    csr_block.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
    122
    123
    124
    125
    126
    // formal: cover 4 expect reached slang top csr_review
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule
    csr_block.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
    122
    123
    124
    125
    126
    // formal: cover 6 expect fail slang top csr_review define NOID=1
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule
    csr_block.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
    122
    123
    124
    125
    126
    // formal: cover 6 expect reached slang top csr_review define BUG=2 define OUTA=1
    `define FORMAL
    // csr_block: four 8-bit registers on a simple synchronous write/read port.
    //   addr 0 CTRL   read/write, reset 0x00
    //   addr 1 STATUS bit0 DONE, bit1 ERR: write-1-to-clear, set by hardware pulses
    //                 set_done / set_err (a set wins over a clear in the same cycle);
    //                 bits 7:2 read 0, reset 0x00
    //   addr 2 ID     read-only, constant 0xA5 (writes ignored)
    //   addr 3 DATA   read/write, reset 0x3C
    // Write: wr_en with addr and wdata, takes effect at the clock edge. Read is combinational
    // on addr (rdata = selected register, current value). Synchronous active-high reset.
    // BUG: 0 none, 1 W1C writes 1 (writing 1 sets the bit instead of clearing it),
    // 2 ID is writable, 3 DATA reset value wrong (0x00 instead of 0x3C).
    module csr_block #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err,
      output wire [7:0] rdata
    );
      reg [7:0] ctrl, data, id_r;
      reg [1:0] status;                       // [0] DONE, [1] ERR
    
      wire [7:0] id_val = (BUG == 2) ? id_r : 8'hA5;
      assign rdata = (addr == 2'd0) ? ctrl :
                     (addr == 2'd1) ? {6'b0, status} :
                     (addr == 2'd2) ? id_val : data;
    
      wire w_ctrl   = wr_en && addr == 2'd0;
      wire w_status = wr_en && addr == 2'd1;
      wire w_id     = wr_en && addr == 2'd2;
      wire w_data   = wr_en && addr == 2'd3;
    
      wire [1:0] hw_set = {set_err, set_done};
      wire [1:0] after_w = (BUG == 1) ? (status | wdata[1:0])            // writing 1 sets
                                      : (status & ~wdata[1:0]);          // writing 1 clears
    
      always @(posedge clk) begin
        if (rst) begin
          ctrl   <= 8'h00;
          status <= 2'b00;
          data   <= (BUG == 3) ? 8'h00 : 8'h3C;
          id_r   <= 8'hA5;
        end else begin
          if (w_ctrl) ctrl <= wdata;
          if (w_data) data <= wdata;
          if (w_id && BUG == 2) id_r <= wdata;
          status <= (w_status ? after_w : status) | hw_set;
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- state-based safety: always true after the reset cycle ----
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);       // ID reads 0xA5
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);   // unimplemented bits read 0
      end
    
      // ---- transition properties: they look one cycle back, so skip the cycle after reset ----
      always @(posedge clk) if (!init) begin
        // reset values, visible on the first cycle after a reset edge
        if ($past(rst)) begin
          a_rst_ctrl:   assert (ctrl == 8'h00);
          a_rst_status: assert (status == 2'b00);
          a_rst_data:   assert (data == 8'h3C);
        end
        if (!$past(rst)) begin
          // read-write registers read back what was written
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          // write-1-to-clear: a 1 written to a bit clears it unless hardware sets it that cycle
          a_w1c_done: assert (!($past(w_status) && $past(wdata[0]) && !$past(set_done)) || !status[0]);
          a_w1c_err:  assert (!($past(w_status) && $past(wdata[1]) && !$past(set_err))  || !status[1]);
          // write-1-to-clear: writing 0 leaves the bit alone (it stays set)
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          // hardware set always wins
          a_hw_set:   assert (!$past(set_done) || status[0]);
          // other registers do not change on a write to a different address
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
    
      // covers: the interesting events can happen
      always @(*) if (!init) begin
        c_w1c_clear: cover (w_status && wdata[0] && status[0] && !set_done);
        c_both_bits: cover (status == 2'b11);
        c_id_write:  cover (w_id);
      end
    `endif
    endmodule
    
    // csr_review: the register block behind an environment that carries the assumptions under review.
    // The first four assumptions are always on. NOID=1 adds the fifth, OUTA=1 adds the sixth.
    module csr_review #(parameter BUG = 0, parameter NOID = 0, parameter OUTA = 0) (
      input  wire       clk,
      input  wire       rst,
      input  wire       wr_en,
      input  wire [1:0] addr,
      input  wire [7:0] wdata,
      input  wire       set_done,
      input  wire       set_err
    );
      wire [7:0] rdata;
      csr_block #(.BUG(BUG)) u_dut (.clk(clk), .rst(rst), .wr_en(wr_en), .addr(addr), .wdata(wdata),
                                    .set_done(set_done), .set_err(set_err), .rdata(rdata));
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) begin
    `ifdef FORMAL
        if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5
    `endif
      end
    endmodule

    The ledger checker reads the assumption lines of the harness text, not a typed flag. It rejects a ledger whose row count differs from the number of assumptions, a discharge that is not an assertion label ("none" included), an assumption that mentions a design output (a deliberately strict version of the rule: an assume must bind an input, and it may read state to say when, but this checker refuses even that so that no output slips in as a left-hand side), and a bound that every value of the signal satisfies.

    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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import re
    HARNESS = """    if (init) assume (rst);                                  // 1: the first cycle is a reset
        if (rst) assume (!wr_en);                                // 2: no register write during reset
        if (rst) assume (!set_done && !set_err);                 // 3: no hardware set pulse during reset
        assume (addr <= 2'd3);                                   // 4: addresses are 0 to 3
        if (NOID) assume (!(wr_en && addr == 2'd2));             // 5: nobody writes the read-only ID register
        if (OUTA) assume (addr != 2'd2 || rdata == 8'hA5);       // 6: the ID register always reads 0xA5"""
    LABELS = ["a_id_const","a_status_hi","a_rst_ctrl","a_rst_status","a_rst_data","a_ctrl_rw","a_data_rw","a_w1c_done","a_w1c_err","a_w1c_zero","a_hw_set","a_isolate_ctrl","a_isolate_data"]   # assertion labels in the block's file
    NEIGHBOUR = ["a_no_wr_in_reset", "a_no_set_in_reset", "a_addr_range"]   # owed by the bus master and the event source
    KINDS = {"reset", "interface", "mode", "timing", "temporary", "abstraction"}
    OUTPUTS = {"rdata"}
    WIDTH = {"addr": 2}
    
    def review(rows, text):
        problems = []
        exprs = [e.strip() for e in re.findall(r"assume \((.*)\);", text)]
        if len(rows) != len(exprs):
            problems.append("ledger has %d rows, harness has %d assumptions" % (len(rows), len(exprs)))
        for name, kind, owner, label, expr in rows:
            if kind not in KINDS or not owner:
                problems.append(name + ": kind or owner missing")
            if label not in LABELS + NEIGHBOUR:
                problems.append(name + ": discharge %r is not an assertion" % label)
            if expr not in exprs:
                problems.append(name + ": expression not in the harness")
            if any(o in expr for o in OUTPUTS):
                problems.append(name + ": assumes a design output")
            m = re.match(r"(\w+) <= 2'd(\d+)", expr)
            if m and int(m.group(2)) >= 2 ** WIDTH[m.group(1)] - 1:
                problems.append(name + ": bound every value satisfies")
        return problems
    
    rows = [("reset first", "reset", "designer", "a_rst_ctrl", "rst"),
            ("no write in reset", "interface", "bus master", "a_no_wr_in_reset", "!wr_en"),
            ("no set in reset", "interface", "event source", "a_no_set_in_reset", "!set_done && !set_err"),
            ("address range", "interface", "bus master", "a_addr_range", "addr <= 2'd3"),
            ("no ID write", "temporary", "nobody", "none", "!(wr_en && addr == 2'd2)"),
            ("ID reads A5", "interface", "designer", "a_id_const", "addr != 2'd2 || rdata == 8'hA5")]
    found = review(rows, HARNESS)
    assert any("not an assertion" in p for p in found), found
    assert any("assumes a design output" in p for p in found), found
    assert any("every value satisfies" in p for p in found), found
    good = review(rows[:3], "\n".join(HARNESS.splitlines()[:3]))
    assert good == [], good
    assert any("rows" in p for p in review(rows[:3], HARNESS))
    print("PASS: ledger rejects an undischarged row, an output assume, a vacuous bound and row-count drift; the sound rows pass")

    Finally the share of input space the assumptions remove, by enumeration, for an opcode assumption op < 12 && op != 5 and an alignment assumption on the two low address bits (illustrative widths):

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Share of the input space an assumption set leaves, by enumeration (illustrative: 4-bit opcode, 2 low address bits).
    space = [(op, lo) for op in range(16) for lo in range(4)]
    def kept(pred):
        return [c for c in space if pred(c)]
    k = kept(lambda c: c[0] < 12 and c[0] != 5 and c[1] == 0)
    assert len(space) == 64 and len(k) == 11
    share = len(k) / len(space)
    assert abs(share - 11 / 64) < 1e-12 and round(100 * share, 1) == 17.2
    bug_cases = [(5, 0), (3, 2)]                   # opcode 5, and an unaligned address
    assert all(c not in k for c in bug_cases)      # a fault that needs either case is invisible under these assumptions
    loose = kept(lambda c: c[0] < 12 and c[0] != 5)
    assert len(loose) == 44 and (5, 0) not in loose and (3, 2) in loose   # dropping alignment brings the unaligned case back
    print("PASS: 11 of 64 inputs remain (17.2 percent); both fault cases are excluded")

    What a reviewer looks for

    • A ledger row for every assumption line, with a discharge that is an assertion label, and none that assume an output.

    • An activity cover per input class an assumption might remove, and a mutation per read-only or guarded behaviour.

    • The share of input space removed is quoted, and no fault case sits wholly inside it.

    Verify and debug

    Verify by deletion. With assumptions 1 to 4 the ID-write fault fails at depth 3; with assumption 5 or 6 added it passes to depth 8. The assumption whose removal brings the failure back is the one that was hiding it. Then read the covers: with assumption 5 the write cover is not reached within depth 6, and with assumption 6 it is reached, so a reached cover does not clear an assumption on an output; only the deletion run does. Quote the share of input space removed (11 of 64, 17.2 per cent) next to the result.

    The assume on a design output. Symptom: the proof passes and the planted fault, which changes that output, passes too. Cause: the engineer assumed the property instead of asserting it. Fix: assumptions go on inputs and documented cut points only. Found by: the ledger rule on output names, and a mutation that survives.

    "Do you believe a three-second proof?" Not yet: list the assumptions, find the discharge of each, cover the traffic each might remove, mutate the design and quote the share of input space left.

    Check yourself Assumption 6 leaves the ID-write cover reachable. Why is it still wrong?

    It removes every trace in which the ID register changes, because the assumption forces the output to 0xA5 whatever the design does. The cover only asks for a write; the faulty design is pruned, not the write.

    Related topics

    • Verification First Principles: Assumptions and Over-Constraint

    • Cover Properties

    • Previous: Assume Versus Assert by Side

    • Next: Turning Assumptions into Assertions

    Previous

    47 / 183

    Next: Turning Assumptions into Assertions

    Up next

    Up nextTurning Assumptions into Assertions

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Assumption Taxonomy and Ownership