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. Black Boxes and Their Soundness Condition
On this page

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

    Black Boxes and Their Soundness Condition

    A black box frees its outputs: passes stay valid, failures may be false, and an assumed contract that the real block breaks gives a false pass.

    Why it matters

    A team boxed the multiplier stage, assumed that it returns the product, and reported the pipeline proved. The real stage had a fault. Job of this lesson: say what a black box keeps and adds, show the failure and the pass it can produce, and show why the assumption on its output is sound only when proved on the real block.

    The core idea

    Boxing a block makes its outputs free inputs. That adds behaviours, so a proof that passes with the box stays valid for the real design. A failure may use a value the real block cannot produce. A contract on the box's outputs removes the false failure, but an assumption is a promise: it is sound only if the same statement is proved on the real block.

    Run

    Stage 2

    Contract

    Result

    Local stage-3 relation

    Boxed

    None

    Proven: a pass with the box is valid

    End to end

    Boxed

    None

    Fails at step 4: a false failure

    End to end

    Boxed

    Assumed

    Proven

    End to end, real stage has a fault

    Boxed

    Assumed only

    Proven: a false pass

    Contract on the real stage

    Real, with the fault

    Asserted

    Fails at step 3: the discharge

    Worked example

    pipe3c.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
    // formal: prove 5 expect pass slang top pipe3c define CUT=1 define CONTRACT=0 define E2E=0
    `define FORMAL
    // pipe3c: pipe3 with stage 2 optionally cut free. CUT=1: the stage-2 register loads the free input pf instead of the
    // product. CONTRACT=1 assumes pf equals the product of the stage-1 registers. BUG=1 plants a fault in the real stage 2
    // (the product plus one). E2E=0 switches the end-to-end property off.
    module pipe3c #(parameter CUT = 0, parameter CONTRACT = 0, parameter BUG = 0, parameter E2E = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      input  wire [7:0] pf,
      output reg  [7:0] y
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      wire [7:0] real_p = s[3:0] * c1 + ((BUG == 1) ? 8'd1 : 8'd0);
      always @(posedge clk) begin
        if (rst) begin s <= 0; c1 <= 0; p <= 0; y <= 0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (CUT == 1) ? pf : real_p;
          y  <= p + 8'd1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // clean cycles since reset, and the inputs three cycles ago
      reg [1:0] hist;
      always @(posedge clk) if (rst) hist <= 0; else if (hist != 3) hist <= hist + 1;
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
      generate if (E2E) begin : g_e2e
        always @(*) if (!init && hist == 3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      end endgenerate
      // a local relation of stage 3, true with the stage cut or not
      always @(posedge clk) if (!init && !$past(rst)) a_stage3: assert (y == $past(p) + 8'd1);
      // the contract on the stage-2 value: assumed on the free input when the stage is cut ...
      generate if (CUT == 1 && CONTRACT == 1) begin : g_c
        always @(*) if (!init) assume (pf == s[3:0] * c1);
      end endgenerate
      // ... and asserted on the real stage otherwise (each operand's past first, then the product, in eight bits)
      generate if (CUT == 0) begin : g_p
        always @(posedge clk) if (!init && !$past(rst)) a_contract: assert (p == $past(s[3:0]) * $past(c1));
      end endgenerate
    `endif
    endmodule
    pipe3c.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
    // formal: bmc 5 expect fail slang top pipe3c define CUT=1 define CONTRACT=0 define BUG=0
    `define FORMAL
    // pipe3c: pipe3 with stage 2 optionally cut free. CUT=1: the stage-2 register loads the free input pf instead of the
    // product. CONTRACT=1 assumes pf equals the product of the stage-1 registers. BUG=1 plants a fault in the real stage 2
    // (the product plus one). E2E=0 switches the end-to-end property off.
    module pipe3c #(parameter CUT = 0, parameter CONTRACT = 0, parameter BUG = 0, parameter E2E = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      input  wire [7:0] pf,
      output reg  [7:0] y
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      wire [7:0] real_p = s[3:0] * c1 + ((BUG == 1) ? 8'd1 : 8'd0);
      always @(posedge clk) begin
        if (rst) begin s <= 0; c1 <= 0; p <= 0; y <= 0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (CUT == 1) ? pf : real_p;
          y  <= p + 8'd1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // clean cycles since reset, and the inputs three cycles ago
      reg [1:0] hist;
      always @(posedge clk) if (rst) hist <= 0; else if (hist != 3) hist <= hist + 1;
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
      generate if (E2E) begin : g_e2e
        always @(*) if (!init && hist == 3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      end endgenerate
      // a local relation of stage 3, true with the stage cut or not
      always @(posedge clk) if (!init && !$past(rst)) a_stage3: assert (y == $past(p) + 8'd1);
      // the contract on the stage-2 value: assumed on the free input when the stage is cut ...
      generate if (CUT == 1 && CONTRACT == 1) begin : g_c
        always @(*) if (!init) assume (pf == s[3:0] * c1);
      end endgenerate
      // ... and asserted on the real stage otherwise (each operand's past first, then the product, in eight bits)
      generate if (CUT == 0) begin : g_p
        always @(posedge clk) if (!init && !$past(rst)) a_contract: assert (p == $past(s[3:0]) * $past(c1));
      end endgenerate
    `endif
    endmodule
    pipe3c.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
    // formal: prove 5 expect pass slang top pipe3c define CUT=1 define CONTRACT=1 define BUG=0
    `define FORMAL
    // pipe3c: pipe3 with stage 2 optionally cut free. CUT=1: the stage-2 register loads the free input pf instead of the
    // product. CONTRACT=1 assumes pf equals the product of the stage-1 registers. BUG=1 plants a fault in the real stage 2
    // (the product plus one). E2E=0 switches the end-to-end property off.
    module pipe3c #(parameter CUT = 0, parameter CONTRACT = 0, parameter BUG = 0, parameter E2E = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      input  wire [7:0] pf,
      output reg  [7:0] y
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      wire [7:0] real_p = s[3:0] * c1 + ((BUG == 1) ? 8'd1 : 8'd0);
      always @(posedge clk) begin
        if (rst) begin s <= 0; c1 <= 0; p <= 0; y <= 0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (CUT == 1) ? pf : real_p;
          y  <= p + 8'd1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // clean cycles since reset, and the inputs three cycles ago
      reg [1:0] hist;
      always @(posedge clk) if (rst) hist <= 0; else if (hist != 3) hist <= hist + 1;
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
      generate if (E2E) begin : g_e2e
        always @(*) if (!init && hist == 3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      end endgenerate
      // a local relation of stage 3, true with the stage cut or not
      always @(posedge clk) if (!init && !$past(rst)) a_stage3: assert (y == $past(p) + 8'd1);
      // the contract on the stage-2 value: assumed on the free input when the stage is cut ...
      generate if (CUT == 1 && CONTRACT == 1) begin : g_c
        always @(*) if (!init) assume (pf == s[3:0] * c1);
      end endgenerate
      // ... and asserted on the real stage otherwise (each operand's past first, then the product, in eight bits)
      generate if (CUT == 0) begin : g_p
        always @(posedge clk) if (!init && !$past(rst)) a_contract: assert (p == $past(s[3:0]) * $past(c1));
      end endgenerate
    `endif
    endmodule

    The first run proves a relation of stage 3 that does not look at the boxed value, so it stays true of the real design. The second is the false failure (depth 4 passes). The third assumes the contract. Now the real stage gets a fault: its product is one too high.

    pipe3c.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
    // formal: prove 5 expect pass slang top pipe3c define CUT=1 define CONTRACT=1 define BUG=1
    `define FORMAL
    // pipe3c: pipe3 with stage 2 optionally cut free. CUT=1: the stage-2 register loads the free input pf instead of the
    // product. CONTRACT=1 assumes pf equals the product of the stage-1 registers. BUG=1 plants a fault in the real stage 2
    // (the product plus one). E2E=0 switches the end-to-end property off.
    module pipe3c #(parameter CUT = 0, parameter CONTRACT = 0, parameter BUG = 0, parameter E2E = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      input  wire [7:0] pf,
      output reg  [7:0] y
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      wire [7:0] real_p = s[3:0] * c1 + ((BUG == 1) ? 8'd1 : 8'd0);
      always @(posedge clk) begin
        if (rst) begin s <= 0; c1 <= 0; p <= 0; y <= 0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (CUT == 1) ? pf : real_p;
          y  <= p + 8'd1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // clean cycles since reset, and the inputs three cycles ago
      reg [1:0] hist;
      always @(posedge clk) if (rst) hist <= 0; else if (hist != 3) hist <= hist + 1;
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
      generate if (E2E) begin : g_e2e
        always @(*) if (!init && hist == 3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      end endgenerate
      // a local relation of stage 3, true with the stage cut or not
      always @(posedge clk) if (!init && !$past(rst)) a_stage3: assert (y == $past(p) + 8'd1);
      // the contract on the stage-2 value: assumed on the free input when the stage is cut ...
      generate if (CUT == 1 && CONTRACT == 1) begin : g_c
        always @(*) if (!init) assume (pf == s[3:0] * c1);
      end endgenerate
      // ... and asserted on the real stage otherwise (each operand's past first, then the product, in eight bits)
      generate if (CUT == 0) begin : g_p
        always @(posedge clk) if (!init && !$past(rst)) a_contract: assert (p == $past(s[3:0]) * $past(c1));
      end endgenerate
    `endif
    endmodule
    pipe3c.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
    // formal: bmc 4 expect fail slang top pipe3c define CUT=0 define CONTRACT=0 define BUG=1
    `define FORMAL
    // pipe3c: pipe3 with stage 2 optionally cut free. CUT=1: the stage-2 register loads the free input pf instead of the
    // product. CONTRACT=1 assumes pf equals the product of the stage-1 registers. BUG=1 plants a fault in the real stage 2
    // (the product plus one). E2E=0 switches the end-to-end property off.
    module pipe3c #(parameter CUT = 0, parameter CONTRACT = 0, parameter BUG = 0, parameter E2E = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      input  wire [7:0] pf,
      output reg  [7:0] y
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      wire [7:0] real_p = s[3:0] * c1 + ((BUG == 1) ? 8'd1 : 8'd0);
      always @(posedge clk) begin
        if (rst) begin s <= 0; c1 <= 0; p <= 0; y <= 0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= (CUT == 1) ? pf : real_p;
          y  <= p + 8'd1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // clean cycles since reset, and the inputs three cycles ago
      reg [1:0] hist;
      always @(posedge clk) if (rst) hist <= 0; else if (hist != 3) hist <= hist + 1;
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
      generate if (E2E) begin : g_e2e
        always @(*) if (!init && hist == 3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      end endgenerate
      // a local relation of stage 3, true with the stage cut or not
      always @(posedge clk) if (!init && !$past(rst)) a_stage3: assert (y == $past(p) + 8'd1);
      // the contract on the stage-2 value: assumed on the free input when the stage is cut ...
      generate if (CUT == 1 && CONTRACT == 1) begin : g_c
        always @(*) if (!init) assume (pf == s[3:0] * c1);
      end endgenerate
      // ... and asserted on the real stage otherwise (each operand's past first, then the product, in eight bits)
      generate if (CUT == 0) begin : g_p
        always @(posedge clk) if (!init && !$past(rst)) a_contract: assert (p == $past(s[3:0]) * $past(c1));
      end endgenerate
    `endif
    endmodule

    The fourth run passes: the box hides the fault, and the contract that should have caught it is only assumed. The fifth removes the box and proves the same contract on the faulty real stage: it fails at step 3 (depth 3 passes), before the end-to-end property would at step 4. That run is the discharge, and the table of contracts needs it for every box.

    What a reviewer looks for

    • Each boxed block has a contract, the proof of that contract on the real block, and a planted fault that fails the proof.

    • A pass obtained with a box is accepted as valid for the real design; a failure is replayed on the real block before anyone tightens an assumption.

    • No assumption on a box's output is written to make a proof go through: it states a property of the real block that has its own proof.

    Verify and debug

    Plant a fault in the real block and rerun the box proof: it must still pass (the box hides it) and the contract proof on the real block must fail. If the contract proof also passes, the contract is too weak to matter.

    The black box that leaks. Symptom: a false counterexample, or a silent false pass through a boxed submodule. Cause: the output left fully free (too coarse), or assumed to a convenient value with no proof (too fine to be true). Fix: constrain from the interface contract and prove the contract on the real block. Found by: replaying the trace on the real block, and by a fault in it that the boxed proof does not see.

    "What does boxing a block do to a proof?" It adds behaviours: passes stay valid, failures may be false. A contract fixes the failures, and is sound only if proved on the real block. Show the five runs.

    Check yourself The boxed proof passes while the real stage has a fault. Is the box unsound?

    The box is sound; the contract is not. A pass with free outputs is valid for the real design. The false pass came from assuming the contract without proving it.

    Related topics

    • Cut Points and Free Outputs

    • Previous: Circular Assume-Guarantee and the False Pass

    • Next: Proving the Contract on the Neighbour

    Previous

    55 / 183

    Next: Proving the Contract on the Neighbour

    Up next

    Up nextProving the Contract on the Neighbour

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Assume-Guarantee Contracts