Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Glossary
  • Metrics Reference
  • Formal Cheat Sheet
  • Formal Foundations19
  • Properties and SVA16
  • Property Development and Constraints19
  • Proof Engines and Convergence23
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
    • The Formal Testplan
      • What Goes to Formal
      • Bring-Up Order
      • Testplan Worked Example
    • Managing Complexity in a Project
    • Debugging Formal Results
    • Formal in Regression
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←The Formal Testplan
  1. Topics
  2. Formal Verification
  3. Methodology and Debug
  4. The Formal Testplan
  5. What Goes to Formal
On this page

    Part 8 of 10, lesson 1 of 13Part 8 of 10/Methodology and Debug/Lesson 1 of 13All levels

    What Goes to Formal

    Which requirements earn a proof: control, protocol and register rules go to formal; deep arithmetic and software flows stay elsewhere.

    Why it matters

    A team pointed formal at a 32-bit multiplier and a firmware boot sequence, waited weeks, and concluded formal does not work, yet never pointed it at the arbiter that starved a requester in silicon. The failure was the selection, not the engine: formal is strong on control, protocol and register rules over all input sequences, and weak on wide arithmetic and long system flows.

    Job of this lesson: decide, per requirement, whether it gets a formal obligation, and show what the obligation looks like when it does.

    The core idea

    A requirement is formal-shaped when control state and interface rules decide it within a bounded neighbourhood of cycles, and simulation-shaped when it needs deep arithmetic, long histories, software or analogue behaviour:

    The requirement is decided by...

    Method

    Example

    Control and protocol rules over all input sequences

    Formal

    rr_arbiter one-hot grant; hs_bridge hold rules

    A bounded neighbourhood of cycles after a trigger

    Formal

    pipe3 three-cycle latency

    Register access types and reset values

    Formal

    csr_block registers

    Equality of two structures, or connectivity under a mode

    Formal

    pipe3 against its retimed twin

    Wide arithmetic, or a long history

    Simulation

    A 32-bit multiplier

    Throughput over long traffic, software, analogue

    Simulation

    hs_bridge sustained throughput

    Applied to the register block and the skid stage, the plan rows follow. Every formal row has a trigger cover, because a property whose trigger never fires proves nothing:

    Id

    Requirement

    Method

    Property

    Trigger cover

    Owner

    CSR-1

    ID register always reads 0xA5

    Formal

    a_id_const

    c_id_write

    Block owner

    CSR-2

    Write-1-to-clear clears a set bit unless hardware sets it

    Formal

    a_w1c_done, a_w1c_err

    c_w1c_clear

    Block owner

    CSR-3

    Reset values after reset

    Formal

    a_rst_ctrl, a_rst_status, a_rst_data

    c_reset_released

    Block owner

    HS-1

    Nothing lost or duplicated under backpressure

    Formal

    a_conserve

    c_skid_used

    Block owner

    HS-2

    A stalled output holds valid and data

    Formal

    a_valid_hold, a_data_hold

    c_stall

    Block owner

    HS-3

    Ready has no combinational path from the output

    Formal

    a_no_ready_comb

    c_backpress_full

    Block owner

    HS-4

    One word per cycle sustained

    Simulation

    Throughput measurement

    Sustained traffic

    Performance team

    Worked example: the register proof and its failing partner

    The register block has a read-write control register, a two-bit write-1-to-clear status with hardware set pulses, a read-only constant and a data register. The first run proves all 13 assertions: prove 4 is the bounded check to depth 4 plus the induction step with k = 4, and both pass, so the result is proven for this model with reset assumed at step 0:

    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
    // formal: prove 4 expect pass slang top csr_block
    `define FORMAL
    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;
      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])
                                      : (status & ~wdata[1:0]);
      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);
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);
      end
      always @(posedge clk) if (!init) begin
        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
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          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]);
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          a_hw_set:   assert (!$past(set_done) || status[0]);
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
      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

    The failing partner injects the planted write-1-to-clear bug, where writing 1 sets the bit instead of clearing it. The bounded check fails at depth 4 on a_w1c_err:

    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
    // formal: bmc 4 expect fail slang top csr_block define BUG=1
    `define FORMAL
    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;
      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])
                                      : (status & ~wdata[1:0]);
      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);
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);
      end
      always @(posedge clk) if (!init) begin
        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
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          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]);
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          a_hw_set:   assert (!$past(set_done) || status[0]);
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
      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

    Read the trace from reset: at step 1 a write of 1 reaches the status register with no hardware set; at step 2 the bit reads 1; at step 3 the clocked property reports it. Root cause is the inverted clear term; the fix restores the mask. A proof is worth its triggers, so the third block reaches the covers on the good design. This copy adds two covers to the design's own three, c_reset_released and c_ctrl_write, which rows CSR-3 and CSR-4 need; they are additions of this lesson, and the capstone lab adds a different fourth cover to its own copy. All five are reached within depth 4: the write and reset covers at step 1, c_w1c_clear and c_both_bits at step 2.

    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
    // formal: cover 4 expect reached slang top csr_block
    `define FORMAL
    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;
      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])
                                      : (status & ~wdata[1:0]);
      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);
      always @(*) if (!init) begin
        a_id_const:  assert (addr != 2'd2 || rdata == 8'hA5);
        a_status_hi: assert (addr != 2'd1 || rdata[7:2] == 6'b0);
      end
      always @(posedge clk) if (!init) begin
        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
          a_ctrl_rw: assert (!($past(w_ctrl)) || ctrl == $past(wdata));
          a_data_rw: assert (!($past(w_data)) || data == $past(wdata));
          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]);
          a_w1c_zero: assert (!($past(w_status) && !$past(wdata[0]) && $past(status[0])) || status[0]);
          a_hw_set:   assert (!$past(set_done) || status[0]);
          a_isolate_ctrl: assert ($past(w_ctrl) || ctrl == $past(ctrl));
          a_isolate_data: assert ($past(w_data) || data == $past(data));
        end
      end
      reg rst_q;
      always @(posedge clk) rst_q <= rst;
      always @(*) if (!init) begin
        c_reset_released: cover (rst_q && !rst);
        c_ctrl_write:     cover (w_ctrl);
        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

    What the review looks for

    • Every formal row names a requirement, a property, a trigger cover and an owner.

    • Simulation rows say why formal was refused.

    • No row claims a proof where only a bounded run exists; the run and depth are on the row.

    Verify and debug

    Ask of each row which input sequences the method examines: formal answers all of them, simulation the sampled ones. A formal row with no trigger cover gets one before the pass is believed; a simulation row that keeps failing on control logic moves to formal.

    Formal aimed at the wrong target: weeks spent proving a wide multiplier while an arbiter starves. Symptom: a slow inconclusive run on arithmetic and no obligation on the control. Cause: selecting by block size instead of shape. Fix: apply the criteria table first. Found by the plan review, before any run.

    “When is formal the wrong tool?” Wide arithmetic, long system flows and real software, where the state space or history is beyond any engine; it is right for control, protocol, arbiters, FIFOs, registers and connectivity. One example each: hold-during-stall must hold for every input sequence, so it is formal; sustained throughput is a measurement over long traffic, so it is simulation.

    Check yourself A requirement says the skid stage must sustain one word per cycle. Formal or simulation, and why?

    Simulation: it is a throughput claim over long traffic, not a control rule over all sequences. The loss-and-duplication rule beside it goes to formal.

    Related topics

    • Next: Bring-Up Order

    • VFP: Combining Formal and Simulation

    • VFP: Planning and Risk

    • UVM: SVA and Formal

    Previous

    132 / 183

    Next: Bring-Up Order

    Up next

    Up nextBring-Up Order

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Testplan Worked Example