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. Bounded Proofs

Part 7 · Formal and Simulation · Senior (S)

Bounded Proofs

Bounded model checking asks whether any input sequence of up to N cycles from reset breaks a property. A pass means no bug within N cycles, not no bug at all. Two runnable bounded proofs of a FIFO, one passing and one returning a five-step counterexample, and DCRouter's measured cost of depth: 3 seconds at depth 6, 41 at depth 8, over five minutes at depth 10.

On this page

    Note

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

    Before this: What Formal Does

    A formal tool reports "PASSED" for a FIFO property. A reviewer asks the question that matters: passed for what? The answer is in the command line: depth 12. The tool considered every input sequence of up to 12 cycles after reset. A bug that needs 13 cycles to appear is outside the question it answered.

    What does a bounded proof tell you, and what does it not?

    The obvious answer, and why it breaks

    The obvious reading of a passing formal check is "proved". A bounded check proves something narrower: no input sequence of length up to N, starting from reset, breaks the property. If every interesting state is reachable within N cycles, that is as good as a proof; if some are not (a counter that needs 255 events, a FIFO 64 deep, a timer of 1,000 cycles), the bounded check never looked at them.

    The idea: unroll N cycles and ask the solver

    • Unrolling: the tool makes N copies of the design's logic, one per cycle, connected through the registers, starting from the reset state (fact V2).

    • The question: is there any assignment of the inputs in those N cycles that makes an assertion false in any of them, while every assumption holds?

    • The answer: a counterexample of at most N cycles, usually the shortest, or a pass meaning "none within N".

    • The cost: grows quickly with N and with the amount of state; it is a search, not a simulation.

    Build it: a bounded proof of a FIFO

    A 4-entry FIFO, with push, pop and data free: the solver considers every sequence of them. Three properties: occupancy never exceeds 4; full and empty agree with it; a refused push changes nothing. The run uses the course's open-source flow (Yosys with yosys-smtbmc and z3, fact V9).

    fifo_bmc.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
    // formal: bmc 12
    // Lesson 7.2: a bounded proof of a 4-entry FIFO. push, pop and din are free: the solver
    // considers every sequence of them for 12 cycles from reset. Properties: occupancy never
    // exceeds 4, full and empty agree with it, and a refused push leaves the FIFO unchanged.
    module fifo_bmc (input logic clk, rst, push, pop, input logic [7:0] din);
      logic [7:0] mem [0:3];
      logic [1:0] wp, rp;
      logic [2:0] count;
      logic full, empty;
      assign full  = (count == 3'd4);
      assign empty = (count == 3'd0);
      always @(posedge clk)
        if (rst) begin wp <= 0; rp <= 0; count <= 0; end
        else begin
          if (push && !full) begin mem[wp] <= din; wp <= wp + 2'd1; end
          if (pop && !empty) rp <= rp + 2'd1;
          count <= count + 3'(push && !full) - 3'(pop && !empty);
        end
      logic past = 1'b0;
      always @(posedge clk) past <= 1'b1;
      always @(*) assume (rst == !past);               // reset in the first cycle only
      always @(posedge clk) if (past) begin
        assert (count <= 3'd4);
        assert (full == (count == 3'd4) && empty == (count == 3'd0));
        if ($past(push && full && !pop && !rst)) assert (count == $past(count) && wp == $past(wp));
      end
    endmodule

    The bounded check passes to depth 12. The same proof on a FIFO whose full flag rises one entry late:

    fifo_bmc_bug.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
    // formal: bmc 12 expect fail
    // Lesson 7.2: the same proof on a FIFO whose full flag rises one entry late. The solver
    // returns a counterexample: the shortest input sequence that breaks a property.
    module fifo_bmc_bug (input logic clk, rst, push, pop, input logic [7:0] din);
      logic [7:0] mem [0:3];
      logic [1:0] wp, rp;
      logic [2:0] count;
      logic full, empty;
      assign full  = (count > 3'd4);                   // BUG: should be (count == 4)
      assign empty = (count == 3'd0);
      always @(posedge clk)
        if (rst) begin wp <= 0; rp <= 0; count <= 0; end
        else begin
          if (push && !full) begin mem[wp] <= din; wp <= wp + 2'd1; end
          if (pop && !empty) rp <= rp + 2'd1;
          count <= count + 3'(push && !full) - 3'(pop && !empty);
        end
      logic past = 1'b0;
      always @(posedge clk) past <= 1'b1;
      always @(*) assume (rst == !past);               // reset in the first cycle only
      always @(posedge clk) if (past) begin
        assert (count <= 3'd4);
        assert (full == (count == 3'd4) && empty == (count == 3'd0));
        if ($past(push && full && !pop && !rst)) assert (count == $past(count) && wp == $past(wp));
      end
    endmodule

    This one fails, as the first line says it should. The counterexample the solver returns is five steps long: reset, then four pushes with no pop; in step 5 the FIFO holds four words and full is still low, breaking "full agrees with occupancy". Compare lesson 1.3, where a simulation found the same bug only in a test that happened to push five times without popping. The solver found the shortest such test by itself.

    Choosing a depth

    Question

    Why it matters

    How many cycles from reset to the deepest state the property cares about?

    A depth below that cannot reach it: a 4-entry FIFO is full after 4 pushes, so depth 12 covers it with room

    How long are the sequences the rule is about?

    A rule about a 16-flit packet needs at least 16 cycles plus setup

    What does the tool cost at that depth?

    Measured, not guessed: see DCRouter below

    Is a full proof possible instead?

    Induction removes the bound (lesson 7.3)

    Measured on DCRouter with its formal properties (lesson 7.9), one bounded check of the whole router took about 3 seconds at depth 6, 41 seconds at depth 8, and did not finish in five minutes at depth 10. Each extra cycle multiplies the search. DCRouter's FIFO-full bug needs at least 10 cycles (reset, eight words held, then a ninth push) and its credits-during-sleep bug at least 13 (a flit sent, eight idle cycles, sleep, then a credit returned), which is why lesson 7.9's depth-7 bounded check cannot see them; its full proof by induction can.

    How real teams use bounded checks

    Practice

    Purpose

    Bounded checks early, on every property, at modest depth

    Fast counterexamples while the RTL is young

    Depth justified per property in the plan

    A bound that reaches the states the rule needs

    Full proofs, or abstraction, for deep state

    Counters, deep FIFOs and timers beyond any practical bound

    What breaks

    A team (illustrative) ran bounded checks to depth 20 on a 32-entry FIFO controller and reported all properties passed. The overflow logic could only be reached after 32 pushes. Depth 20 never filled the FIFO, so every property about full was vacuously safe within the bound. A parameterised 4-entry configuration (lesson 3.6) or an inductive proof would have reached it.

    Why this matters

    Senior lens. Never accept a bounded pass without its depth and an argument that the depth reaches the states the property is about. Bounded checks are excellent bug-finders and weak proofs; for deep state, ask for induction, abstraction or a smaller configuration.

    Interview

    Asked: "What does a bounded proof tell you?"

    Weak answer: That the design is correct.

    Strong answer: That no input sequence of up to N cycles from reset, among those the assumptions allow, breaks the property. It's a complete search within the bound, so it finds rare corners simulation would miss, and a failure comes with a minimal counterexample. But anything that takes longer than N cycles to reach is unexamined: deep FIFOs, saturating counters, long timers. So I justify the depth per property, and for deep state I use induction, abstraction or a small configuration. On DCRouter the cost grew from 3 seconds at depth 6 to over five minutes at depth 10.

    Follow-ups: (1) How would you choose the depth? (2) What do you do when the needed depth is too expensive?

    Check yourself Why does the buggy FIFO's counterexample have exactly five steps?

    One reset step and four pushes are the shortest way to fill a 4-entry FIFO; in step 5 it holds four words with full still low.

    Check yourself DCRouter's ERR_CNT saturates after 255 errors. Can a bounded check of depth 7 find BUG_15 (wrap instead of saturate)?

    No: reaching 255 errors takes hundreds of cycles; the property needs induction or a smaller counter in a test configuration.

    Key takeaways

    • A bounded check proves no violation within N cycles of reset, and nothing beyond.

    • Counterexamples are short, complete bug reports found without writing a test.

    • Cost grows steeply with depth: justify the bound, and use induction or abstraction for deep state.

    Keep going

    Next: Lesson 7.3, Induction and Full Proofs. Removing the bound: induction and helper invariants.

    Related topics

    • Part 7 overview: Formal and Simulation

    Previous

    73 / 160

    Next: Induction and Full Proofs

    Up next

    Up nextInduction and Full Proofs

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Assumptions and Over-Constraint
    • Where Formal Wins
    • Where Simulation Wins