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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • Meet DCRouter
  • Glossary
  • Part 0: What Verification Is8
  • Part 1: What Can Go Wrong11
  • Part 2: Specifications as Checkable Rules8
  • Part 3: Observability and Controllability7
  • Part 4: Stimulus11
  • Part 5: Checking11
  • Part 6: Coverage10
  • Part 7: Formal and Simulation10
    • What Formal Does
    • Bounded Proofs
    • Induction and Full Proofs
    • Assumptions and Over-Constraint
    • Where Formal Wins
    • Where Simulation Wins
    • Formal Apps
    • Combining Formal and Simulation
    • Proving DCRouter
    • Drills: Formal and Simulation
  • Part 8: Verification Levels and Reuse8
  • Part 9: Planning and Risk9
  • Part 10: Emulation and Prototyping8
  • Part 11: Post-Silicon Validation8
  • Part 12: Bug Economics8
  • Part 13: Debug7
  • Part 14: Sign-Off7
  • Part 15: Verification in Context8
  • Part 16: Case Studies5
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 7: Formal and Simulation
  1. Topics
  2. First Principles of Verification Strategy
  3. Part 7: Formal and Simulation
  4. Proving DCRouter

Part 7 · Formal and Simulation · Senior (S)

Proving DCRouter

DCRouter step 9: designer-owned formal properties for the FIFOs, arbiter and credits, checked with every input free. A depth-7 bounded check catches 7 Bug Zoo bugs in two to six steps; a full proof by induction proves the clean design's properties for every depth and catches 9, including the two deep bugs the bound could not reach. The other 15 break rules no property states.

On this page

    Note

    Level: Senior (S) · Answers: When should we prove instead of test?

    Before this: Induction and Full Proofs, A Coverage Model for DCRouter

    By the end of Part 6, one simulation testbench caught all 24 Bug Zoo bugs, after six lessons of stimulus, checkers and coverage. This lesson takes the opposite approach to part of the design: no stimulus, no scoreboard, no coverage model. A handful of properties about the FIFOs, the arbiter and the credits, every input left free, and a solver.

    Which DCRouter bugs fall to proof, and what does a full proof add?

    The obvious answer, and why it breaks

    The obvious expectation is that formal will either catch everything or choke on a design this size. Neither happens. The bounded check of the whole router is fast at small depths and impractical beyond about 8 cycles (lesson 7.2), so it cannot reach deep states. Induction removes the depth limit, if the properties are inductive. And formal catches exactly the bugs that break the rules its properties state: no more, no fewer.

    Build it: the designer's properties

    The properties live inside DCRouter's RTL under ` ifdef FORMAL `, as designer-owned assertions (lesson 3.4): simulators never compile them, and Yosys's read_verilog -formal does (fact V9). Putting them inside the modules was not a style choice: the Yosys version used here neither resolves hierarchical references nor applies bind` (lesson 7.4). In each input FIFO:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    `ifdef FORMAL
      // Designer properties (R1, R2), checked by formal tools only: occupancy never exceeds the
      // depth, and the count agrees with the pointers (the invariant that makes induction work).
      always @(posedge clk) if (!rst) begin
        assert (count <= 4'd8);
        assert (count[2:0] == 3'(wp - rp));
      end
    `endif

    In the router: credits plus the flits a sink still holds always total 4; every free output with a requester grants the specification's choice of winner (the first requester at or after the round-robin pointer, among high-priority requesters when prio_en is set), routed by its own header; and the pointer moves past each winner. The block also states the one environment rule it needs: a sink returns a credit only for a flit it holds.

    design.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
    `ifdef FORMAL
      // Designer properties (R3, R6, R7), checked by formal tools only (read_verilog -formal
      // defines FORMAL; simulators never see this block). A wrapper supplies the environment.
      logic f_past = 1'b0;
      always @(posedge clk) f_past <= 1'b1;
    
      // R7: an output's credits plus the flits its sink still holds always total 4
      logic [2:0] f_held [0:3];
      always @(posedge clk)
        for (int o = 0; o < 4; o++)
          f_held[o] <= rst ? 3'd0 : f_held[o] + (send[o] ? 3'd1 : 3'd0) - (out_crd[o] ? 3'd1 : 3'd0);
      // environment: a sink returns a credit only for a flit it holds
      always @(*) for (int o = 0; o < 4; o++) if (out_crd[o]) assume (f_held[o] != 3'd0);
    
      // R6: the specification's choice of winner for each free output: the first requester at or
      // after the round-robin pointer, among high-priority requesters if prio_en is set and any exist
      logic [3:0] f_any, f_hi;
      logic [1:0] f_win [0:3];
      always_comb
        for (int o = 0; o < 4; o++) begin
          f_any[o] = 1'b0; f_hi[o] = 1'b0; f_win[o] = 2'd0;
          for (int i = 0; i < 4; i++) if (req[i] && head[i][15:14] == 2'(o)) begin
            f_any[o] = 1'b1;
            if (head[i][13]) f_hi[o] = 1'b1;
          end
          for (int k = 3; k >= 0; k--) begin
            logic [1:0] i;
            i = 2'(rr[o] + 2'(k));
            if (req[i] && head[i][15:14] == 2'(o) && (!(prio_en && f_hi[o]) || head[i][13])) f_win[o] = i;
          end
        end
    
      always @(posedge clk) if (f_past && !rst)
        for (int o = 0; o < 4; o++) begin
          assert ({1'b0, credits[o]} + {1'b0, f_held[o]} == 4'd4);                  // R7
          if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] && gidx[o] == f_win[o]);   // R6, R3
          if ($past(grant[o] && !rst)) assert (rr[o] == $past(gidx[o]) + 2'd1);    // R6 rotation
        end
    `endif

    The harness instantiates DCRouter with every input free and assumes only reset in the first cycle. Its first line asks for a full proof: a 4-cycle bounded check, then a 4-step induction.

    dcr_formal.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
    // formal: prove 4
    // uses: dcrouter
    // DCRouter step 9: a formal harness. Every input is free: any packets, any APB traffic, any
    // timing; the only environment rules are reset in the first cycle and, inside DCRouter's
    // property block, that a sink returns a credit only for a flit it holds. The properties
    // themselves are the designer's, in dcrouter.sv under `ifdef FORMAL`: FIFO occupancy and
    // pointers (R1, R2), the arbiter's choice and rotation (R3, R6), and credits (R7).
    // `prove 4`: a bounded check of 4 cycles, then a 4-step induction, so a pass holds at every depth.
    module dcr_formal (
      input logic clk, rst,
      input logic [3:0]  in_vld, in_last, out_crd,
      input logic [63:0] in_data,
      input logic psel, penable, pwrite,
      input logic [7:0]  paddr,
      input logic [31:0] pwdata
    );
      logic [3:0]  in_rdy, out_vld, out_last, out_err;
      logic [63:0] out_data;
      logic [31:0] prdata;
      logic        pready, pslverr, sleeping;
      dcrouter dut (.*);
      logic past = 1'b0;
      always @(posedge clk) past <= 1'b1;
      always @(*) assume (rst == !past);               // reset in the first cycle only
    endmodule

    The course's checker runs it and reports both steps passing: on the clean design, these properties hold at every depth, for every input sequence. As evidence that the proof can fail, the same harness with BUG_5 switched on must produce a counterexample:

    dcr_formal_bug5.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
    // formal: bmc 7 expect fail
    // uses: dcrouter BUG_5
    // DCRouter step 9: a formal harness. Every input is free: any packets, any APB traffic, any
    // timing; the only environment rules are reset in the first cycle and, inside DCRouter's
    // property block, that a sink returns a credit only for a flit it holds. The properties
    // themselves are the designer's, in dcrouter.sv under `ifdef FORMAL`: FIFO occupancy and
    // pointers (R1, R2), the arbiter's choice and rotation (R3, R6), and credits (R7).
    // With BUG_5 (the round-robin pointer never moves) the bounded check must find a counterexample.
    module dcr_formal_bug5 (
      input logic clk, rst,
      input logic [3:0]  in_vld, in_last, out_crd,
      input logic [63:0] in_data,
      input logic psel, penable, pwrite,
      input logic [7:0]  paddr,
      input logic [31:0] pwdata
    );
      logic [3:0]  in_rdy, out_vld, out_last, out_err;
      logic [63:0] out_data;
      logic [31:0] prdata;
      logic        pready, pslverr, sleeping;
      dcrouter dut (.*);
      logic past = 1'b0;
      always @(posedge clk) past <= 1'b1;
      always @(*) assume (rst == !past);               // reset in the first cycle only
    endmodule

    Run it against the Bug Zoo

    A bounded check to depth 7 for each bug (bug-zoo.mjs --formal 7):

    Diagram
    clean design: PASS
      BUG_1  missed  
      BUG_2  caught  FAIL: counterexample in step 5: assert (count[2:0] == 3'(wp - rp));
      BUG_3  missed  
      BUG_4  missed  
      BUG_5  caught  FAIL: counterexample in step 4: if ($past(grant[o] && !rst)) assert (rr[o] == $past(gidx[o
      BUG_6  caught  FAIL: counterexample in step 3: if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] &&
      BUG_7  caught  FAIL: counterexample in step 3: if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] &&
      BUG_8  missed  
      BUG_9  caught  FAIL: counterexample in step 6: assert ({1'b0, credits[o]} + {1'b0, f_held[o]} == 4'd4); /
      BUG_10 caught  FAIL: counterexample in step 2: assert ({1'b0, credits[o]} + {1'b0, f_held[o]} == 4'd4); /
      BUG_11 missed  
      BUG_12 missed  
      BUG_13 missed  
      BUG_14 missed  
      BUG_15 missed  
      BUG_16 missed  
      BUG_17 missed  
      BUG_18 missed  
      BUG_19 missed  
      BUG_20 missed  
      BUG_21 missed  
      BUG_22 missed  
      BUG_23 missed  
      BUG_24 caught  FAIL: counterexample in step 6: if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] &&
    caught 7 of 24 bugs

    Then the full proof for each bug (--formal 4 --induction):

    Diagram
    clean design: PASS
      BUG_1  caught  FAIL: induction step fails: assert (count <= 4'd8);
      BUG_2  caught  FAIL: induction step fails: assert (count[2:0] == 3'(wp - rp));
      BUG_3  missed  
      BUG_4  missed  
      BUG_5  caught  FAIL: induction step fails: if ($past(grant[o] && !rst)) assert (rr[o] == $past(gidx[o]) +
      BUG_6  caught  FAIL: counterexample in step 3: if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] &&
      BUG_7  caught  FAIL: counterexample in step 3: if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] &&
      BUG_8  missed  
      BUG_9  caught  FAIL: induction step fails: assert ({1'b0, credits[o]} + {1'b0, f_held[o]} == 4'd4); // R7
      BUG_10 caught  FAIL: counterexample in step 2: assert ({1'b0, credits[o]} + {1'b0, f_held[o]} == 4'd4); /
      BUG_11 missed  
      BUG_12 missed  
      BUG_13 missed  
      BUG_14 missed  
      BUG_15 missed  
      BUG_16 missed  
      BUG_17 missed  
      BUG_18 missed  
      BUG_19 missed  
      BUG_20 missed  
      BUG_21 missed  
      BUG_22 caught  FAIL: induction step fails: assert ({1'b0, credits[o]} + {1'b0, f_held[o]} == 4'd4); // R7
      BUG_23 missed  
      BUG_24 caught  FAIL: induction step fails: if (!lock[o] && !sleeping && f_any[o]) assert (grant[o] && gid
    caught 9 of 24 bugs

    How to read it: the bounded check catches 7, each with a counterexample of two to six steps. The full proof catches 9: the same ones, plus BUG_1 and BUG_22, whose effects lie deeper than any practical bound (a full FIFO, a credit returned during sleep). For those two the induction step fails from a state where the bug has already corrupted the count; since the clean design passes the same induction, the failure is the bug's. The 15 missed bugs all break rules no property states: framing, error flags, counters, registers, configuration, sleep and wake.

    The bug-catch matrix with formal

    Bug

    Best Part 4 test

    Checked hunt (5.10)

    Covered hunt (6.9)

    Formal, bounded (depth 7)

    Formal, full proof

    BUG_1

    caught

    caught

    caught

    ·

    caught

    BUG_2

    caught

    caught

    caught

    caught

    caught

    BUG_3

    caught

    caught

    caught

    ·

    ·

    BUG_4

    caught

    caught

    caught

    ·

    ·

    BUG_5

    ·

    caught

    caught

    caught

    caught

    BUG_6

    ·

    caught

    caught

    caught

    caught

    BUG_7

    ·

    caught

    caught

    caught

    caught

    BUG_8

    caught

    caught

    caught

    ·

    ·

    BUG_9

    caught

    caught

    caught

    caught

    caught

    BUG_10

    caught

    caught

    caught

    caught

    caught

    BUG_11

    caught

    caught

    caught

    ·

    ·

    BUG_12

    caught

    caught

    caught

    ·

    ·

    BUG_13

    caught

    caught

    caught

    ·

    ·

    BUG_14

    caught

    caught

    caught

    ·

    ·

    BUG_15

    ·

    ·

    caught

    ·

    ·

    BUG_16

    ·

    ·

    caught

    ·

    ·

    BUG_17

    ·

    ·

    caught

    ·

    ·

    BUG_18

    ·

    ·

    caught

    ·

    ·

    BUG_19

    ·

    ·

    caught

    ·

    ·

    BUG_20

    caught

    caught

    caught

    ·

    ·

    BUG_21

    caught

    caught

    caught

    ·

    ·

    BUG_22

    caught

    caught

    caught

    ·

    caught

    BUG_23

    ·

    ·

    caught

    ·

    ·

    BUG_24

    caught

    caught

    caught

    caught

    caught

    Two different shapes of evidence. Simulation needed six lessons to reach 24, but every rule was covered by something. Formal needed one lesson and caught 9, but for its rules it gives what no simulation can: proof that the FIFOs never overflow, the arbiter always picks the specified winner and no credit is ever lost or invented, for every input sequence, at every depth.

    What this step teaches

    • Formal catches exactly what its properties state: 9 bugs, all in the FIFO, arbitration and credit rules.

    • Bounded checks find shallow bugs fast; induction reaches the deep ones and turns passes into proofs.

    • Formal found arbitration bugs without the hot-spot stimulus simulation needed (lesson 5.10).

    • The two methods complement each other: proof where properties are cheap to state, simulation for the rest (lesson 7.8).

    How real teams use a result like this

    Practice

    Purpose

    Designer properties for control logic, proved early

    Bugs found before testbenches exist; proofs for the riskiest rules

    Planted bugs or mutations run against proofs

    Evidence the proofs check something

    Proved rules marked in the evidence ledger

    Simulation effort moved to rules without proofs (Part 14)

    What breaks

    The first version of these properties failed on the clean design at step 2: the rotation property assumed that a grant in the previous cycle always moves the pointer, but during the reset cycle reset sets the pointer instead. A one-term fix ($past(grant[o] && !rst)) corrected it. Properties are code, and a failing proof on a clean design is a property bug until shown otherwise, the same discipline as validating checkers on the clean design (lesson 5.8).

    Why this matters

    Principal lens. Put proofs where the rules are easy to state and expensive to get wrong: arbitration, conservation laws, occupancy. On DCRouter, a few dozen lines of designer properties proved those rules for all depths and caught every planted bug that broke them. That frees simulation to concentrate on data, counts, configuration and power, where proofs are costly.

    Interview

    Asked: "What did formal add to a router that simulation already verified?"

    Weak answer: Nothing, simulation caught all the bugs.

    Strong answer: Proof. Simulation had caught all 24 planted bugs, but only for the sequences it ran. Designer properties for the FIFOs, arbiter and credits, with every input free, were proved by induction for every depth: the FIFOs never overflow, the arbiter always grants the specified winner, credits are conserved. The bounded check found 7 planted bugs in two to six steps without stimulus; the full proof found 9, including a full-FIFO bug and a sleep-credit bug too deep for any practical bound. It missed 15 because no property stated their rules, which is the reminder that formal checks exactly what you write.

    Follow-ups: (1) Why did the bounded check miss BUG_1? (2) How do you know the proof is not vacuous?

    Check yourself How do you know the clean design's proof is not vacuous?

    The same properties and assumptions produce counterexamples or induction failures for nine planted bugs, including BUG_5's counterexample in the listing; a vacuous proof would pass them all.

    Check yourself BUG_1's induction step fails, but its bounded check passes. Why is that still evidence of a real bug here?

    The clean design passes the same induction, so the failing step comes from the bug's behaviour (accepting a ninth word), not from a missing invariant; a deeper bounded check would confirm it.

    Key takeaways

    • Designer properties for DCRouter's FIFOs, arbiter and credits were proved for every depth by induction.

    • The bounded check caught 7 planted bugs; the full proof caught 9, including the two deep ones.

    • The 15 misses broke rules no property states: formal checks exactly what is written.

    Keep going

    Next: Drills: Formal and Simulation. Interview questions, a whiteboard exercise and a ten-question checkpoint for Part 7.

    Related topics

    • Part 7 overview: Formal and Simulation

    Previous

    80 / 160

    Next: Drills: Formal and Simulation

    Up next

    Up nextDrills: Formal and Simulation

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • What Formal Does
    • Bounded Proofs
    • Induction and Full Proofs