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
    • Abstraction and Complexity
    • Assume-Guarantee and Black Boxes
      • Assume-Guarantee Contracts
      • Circular Assume-Guarantee and the False Pass
      • Black Boxes and Their Soundness Condition
      • Proving the Contract on the Neighbour
    • 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

←Assume-Guarantee and Black Boxes
  1. Topics
  2. Formal Verification
  3. Property Development and Constraints
  4. Assume-Guarantee and Black Boxes
  5. Circular Assume-Guarantee and the False Pass
On this page

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

    Circular Assume-Guarantee and the False Pass

    Two blocks that each assume the other is good both prove, and both are wrong; assuming the guarantee only up to the previous cycle repairs it.

    Why it matters

    Block A is proved on the assumption that B's output is good. Block B is proved on the assumption that A's output is good. Both proofs pass. The system holds a bad flag that circulates for ever. Job of this lesson: run the circle, see it pass falsely, and repair it by assuming the guarantee only up to the previous cycle.

    The core idea

    Each block copies the other's output into a flag one cycle later. In the faulty version nothing clears a flag at reset. Proved alone, block A with the assumption "the other flag is always low" keeps its own flag low from step 1, so the proof passes. The same two blocks in a ring start with arbitrary flags, swap them for ever, and fail the claim "both low" at step 1. The circle has no base case.

    Top-down diagram: after reset both flags are arbitrary; block A copies B's flag and B copies A's, with an arrow from B back to A. From B a second arrow leads to a high flag that circulates for ever, and from that a repair box: assume the other flag was low in the previous cycle, or reset has just happened.
    Each block's proof assumes the other's flag is good, so neither proof checks the start. A flag that is high after reset is copied round the ring for ever. The repair assumes the guarantee only in the previous cycle and asserts it now, so the first cycle after reset is checked.

    The repair is induction on time. Assume the other block's output was good in the previous cycle (and make the assumption vacuous right after a reset cycle), and assert it now. The reset term matters: written without it the assumption silently removes the reset gap.

    Worked example

    MODE=0 is the same-cycle assumption, MODE=1 the time-indexed one, FIX=1 the reset that clears the flag. The first run is the false pass, the second the failing ring:

    blk.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 expect pass slang top unit define FIX=0 define MODE=0
    `define FORMAL
    module blk #(parameter FIX = 0) (input wire clk, input wire rst, input wire other, output reg f);
      always @(posedge clk) begin
        if (FIX == 1 && rst) f <= 1'b0; else f <= other;
      end
    endmodule
    module unit #(parameter FIX = 0, parameter MODE = 0) (input wire clk, input wire rst, input wire other);
      wire f; blk #(.FIX(FIX)) u (.clk(clk), .rst(rst), .other(other), .f(f));
      reg init = 1'b1; reg pother = 1'b0, prst = 1'b1; always @(posedge clk) begin init <= 1'b0; pother <= other; prst <= rst; end
      always @(*) if (init) assume (rst);
      generate if (MODE == 0) begin : g0
        always @(*) assume (!other);
      end else begin : g1
        always @(*) if (!init) assume (!pother || prst);
      end endgenerate
      always @(*) if (!init) a_good: assert (!f);
    endmodule
    module ring #(parameter FIX = 0) (input wire clk, input wire rst);
      wire fa, fb;
      blk #(.FIX(FIX)) ua (.clk(clk), .rst(rst), .other(fb), .f(fa));
      blk #(.FIX(FIX)) ub (.clk(clk), .rst(rst), .other(fa), .f(fb));
      reg init = 1'b1; always @(posedge clk) init <= 1'b0; always @(*) if (init) assume (rst);
      always @(*) if (!init) a_good: assert (!fa && !fb);
    endmodule
    blk.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 4 expect fail slang top ring define FIX=0
    `define FORMAL
    module blk #(parameter FIX = 0) (input wire clk, input wire rst, input wire other, output reg f);
      always @(posedge clk) begin
        if (FIX == 1 && rst) f <= 1'b0; else f <= other;
      end
    endmodule
    module unit #(parameter FIX = 0, parameter MODE = 0) (input wire clk, input wire rst, input wire other);
      wire f; blk #(.FIX(FIX)) u (.clk(clk), .rst(rst), .other(other), .f(f));
      reg init = 1'b1; reg pother = 1'b0, prst = 1'b1; always @(posedge clk) begin init <= 1'b0; pother <= other; prst <= rst; end
      always @(*) if (init) assume (rst);
      generate if (MODE == 0) begin : g0
        always @(*) assume (!other);
      end else begin : g1
        always @(*) if (!init) assume (!pother || prst);
      end endgenerate
      always @(*) if (!init) a_good: assert (!f);
    endmodule
    module ring #(parameter FIX = 0) (input wire clk, input wire rst);
      wire fa, fb;
      blk #(.FIX(FIX)) ua (.clk(clk), .rst(rst), .other(fb), .f(fa));
      blk #(.FIX(FIX)) ub (.clk(clk), .rst(rst), .other(fa), .f(fb));
      reg init = 1'b1; always @(posedge clk) init <= 1'b0; always @(*) if (init) assume (rst);
      always @(*) if (!init) a_good: assert (!fa && !fb);
    endmodule

    The time-indexed assumption exposes the flaw at once: the unit proof now fails at step 1, because the flag has no value of its own after reset. With the reset fix, both the unit and the ring prove:

    blk.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 4 expect fail slang top unit define FIX=0 define MODE=1
    `define FORMAL
    module blk #(parameter FIX = 0) (input wire clk, input wire rst, input wire other, output reg f);
      always @(posedge clk) begin
        if (FIX == 1 && rst) f <= 1'b0; else f <= other;
      end
    endmodule
    module unit #(parameter FIX = 0, parameter MODE = 0) (input wire clk, input wire rst, input wire other);
      wire f; blk #(.FIX(FIX)) u (.clk(clk), .rst(rst), .other(other), .f(f));
      reg init = 1'b1; reg pother = 1'b0, prst = 1'b1; always @(posedge clk) begin init <= 1'b0; pother <= other; prst <= rst; end
      always @(*) if (init) assume (rst);
      generate if (MODE == 0) begin : g0
        always @(*) assume (!other);
      end else begin : g1
        always @(*) if (!init) assume (!pother || prst);
      end endgenerate
      always @(*) if (!init) a_good: assert (!f);
    endmodule
    module ring #(parameter FIX = 0) (input wire clk, input wire rst);
      wire fa, fb;
      blk #(.FIX(FIX)) ua (.clk(clk), .rst(rst), .other(fb), .f(fa));
      blk #(.FIX(FIX)) ub (.clk(clk), .rst(rst), .other(fa), .f(fb));
      reg init = 1'b1; always @(posedge clk) init <= 1'b0; always @(*) if (init) assume (rst);
      always @(*) if (!init) a_good: assert (!fa && !fb);
    endmodule
    blk.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 expect pass slang top unit define FIX=1 define MODE=1
    `define FORMAL
    module blk #(parameter FIX = 0) (input wire clk, input wire rst, input wire other, output reg f);
      always @(posedge clk) begin
        if (FIX == 1 && rst) f <= 1'b0; else f <= other;
      end
    endmodule
    module unit #(parameter FIX = 0, parameter MODE = 0) (input wire clk, input wire rst, input wire other);
      wire f; blk #(.FIX(FIX)) u (.clk(clk), .rst(rst), .other(other), .f(f));
      reg init = 1'b1; reg pother = 1'b0, prst = 1'b1; always @(posedge clk) begin init <= 1'b0; pother <= other; prst <= rst; end
      always @(*) if (init) assume (rst);
      generate if (MODE == 0) begin : g0
        always @(*) assume (!other);
      end else begin : g1
        always @(*) if (!init) assume (!pother || prst);
      end endgenerate
      always @(*) if (!init) a_good: assert (!f);
    endmodule
    module ring #(parameter FIX = 0) (input wire clk, input wire rst);
      wire fa, fb;
      blk #(.FIX(FIX)) ua (.clk(clk), .rst(rst), .other(fb), .f(fa));
      blk #(.FIX(FIX)) ub (.clk(clk), .rst(rst), .other(fa), .f(fb));
      reg init = 1'b1; always @(posedge clk) init <= 1'b0; always @(*) if (init) assume (rst);
      always @(*) if (!init) a_good: assert (!fa && !fb);
    endmodule
    blk.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 expect pass slang top ring define FIX=1
    `define FORMAL
    module blk #(parameter FIX = 0) (input wire clk, input wire rst, input wire other, output reg f);
      always @(posedge clk) begin
        if (FIX == 1 && rst) f <= 1'b0; else f <= other;
      end
    endmodule
    module unit #(parameter FIX = 0, parameter MODE = 0) (input wire clk, input wire rst, input wire other);
      wire f; blk #(.FIX(FIX)) u (.clk(clk), .rst(rst), .other(other), .f(f));
      reg init = 1'b1; reg pother = 1'b0, prst = 1'b1; always @(posedge clk) begin init <= 1'b0; pother <= other; prst <= rst; end
      always @(*) if (init) assume (rst);
      generate if (MODE == 0) begin : g0
        always @(*) assume (!other);
      end else begin : g1
        always @(*) if (!init) assume (!pother || prst);
      end endgenerate
      always @(*) if (!init) a_good: assert (!f);
    endmodule
    module ring #(parameter FIX = 0) (input wire clk, input wire rst);
      wire fa, fb;
      blk #(.FIX(FIX)) ua (.clk(clk), .rst(rst), .other(fb), .f(fa));
      blk #(.FIX(FIX)) ub (.clk(clk), .rst(rst), .other(fa), .f(fb));
      reg init = 1'b1; always @(posedge clk) init <= 1'b0; always @(*) if (init) assume (rst);
      always @(*) if (!init) a_good: assert (!fa && !fb);
    endmodule

    Run

    Result

    Reading

    Unit, same-cycle assumption, no reset fix

    Proven

    False pass: the assumption supplies the base case

    Ring, no reset fix

    Fails at step 1

    The circle proves nothing

    Unit, time-indexed, no reset fix

    Fails at step 1

    No base case: the flag has no reset value

    Unit and ring, time-indexed, reset fix

    Proven

    Base case from reset, step from the previous cycle

    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
    from itertools import product
    # One flag per block. A flag copies the other block's output; with the fix a reset clears it.
    def nxt(other, rst, fix):
        return 0 if (fix and rst) else other
    
    def unit_ok(mode, fix, n=5):
        # block alone: 'other' is a free input; step 0 is a reset; f must be 0 from step 1
        for f0, other, later in product((0, 1), product((0, 1), repeat=n), product((0, 1), repeat=n - 1)):
            rst = (1,) + later
            if mode == 0 and any(other): continue                                # assume (!other) in every step
            if mode == 1 and any(other[t - 1] and not rst[t - 1] for t in range(1, n)): continue   # assume (!pother || prst)
            f = f0
            for t in range(n):
                if t >= 1 and f != 0: return False
                f = nxt(other[t], rst[t], fix)
        return True
    
    def ring_ok(fix, n=5):
        for fa, fb, later in product((0, 1), (0, 1), product((0, 1), repeat=n - 1)):
            rst = (1,) + later
            for t in range(n):
                if t >= 1 and (fa or fb): return False
                fa, fb = nxt(fb, rst[t], fix), nxt(fa, rst[t], fix)
        return True
    
    assert unit_ok(0, 0) is True and ring_ok(0) is False        # the circle: the unit passes, the ring fails
    assert unit_ok(1, 0) is False                                # time-indexed assumption: no base case, caught
    assert unit_ok(1, 1) is True and ring_ok(1) is True          # reset fix: both prove
    print("PASS: same-cycle circle passes falsely; time-indexed assumption exposes it; the reset fix proves unit and ring")

    What a reviewer looks for

    • Every assume-guarantee pair is drawn; a pair that points both ways is rejected or carries an induction-on-time argument.

    • A guarantee assumed about a neighbour is time-indexed: previous cycle in the assumption, current cycle in the assertion, vacuous after reset.

    • The two-direction test: delete one assumption at a time. If a proof that should depend on the other is unchanged in both directions, there is no real dependence; if each needs the other, there is a circle.

    Verify and debug

    Draw each assumption and the assertion that discharges it; a pair of arrows in both directions is the circle. Then run the ring with no assumptions between the blocks: here it fails within depth 4 while the units prove alone under the same-cycle assumption, which means the units lean on each other. Next make the assumption time-indexed and rerun the unit: it fails at step 1, because the flag has no value of its own after reset. That failure is the missing base case showing itself; the reset fix removes it and both the unit and the ring prove.

    Circular assume-guarantee. Symptom: A assumes B's output is good and B assumes A's; both prove, both are wrong. Cause: no base case, so each proof leans on the other. Fix: assume the guarantee up to the previous cycle and assert it now, with a reset term, or break the loop with a concrete model. Found by: drawing the pairs and running the ring with no assumptions.

    "A assumes B and B assumes A: is that sound?" Not as stated. Say induction on time: assume the guarantee in earlier cycles, assert it now, base case from reset. Show the false pass and the step-1 failure that exposes it.

    Check yourself Why does the time-indexed assumption need the || prst term?

    Right after a reset cycle the previous output is not constrained by anything. Without the term the assumption removes that cycle and the base case disappears.

    Related topics

    • K-Induction in Depth

    • Previous: Assume-Guarantee Contracts

    • Next: Black Boxes and Their Soundness Condition

    Previous

    54 / 183

    Next: Black Boxes and Their Soundness Condition

    Up next

    Up nextBlack Boxes and Their Soundness Condition

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Proving the Contract on the Neighbour