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
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
    • The Brief
    • Stage 1: FIFO and Handshake
      • Stage 1 FIFO Property Set
      • Stage 1 Bridge and the Assumption Contract
      • Stage 1 Covers and Mutants
    • Stage 2: Registers and Arbiter
    • Stage 3: Pipeline and Equivalence
    • Stage 4: Induction and Signoff
    • Solutions
    • Appendix: Run It Yourself on an Open-Source Flow
Loading lesson

←Stage 1: FIFO and Handshake
  1. Topics
  2. Formal Verification
  3. Capstone: A Chip-Block Verification Lab
  4. Stage 1: FIFO and Handshake
  5. Stage 1 Bridge and the Assumption Contract
On this page

    Part 10 of 10, lesson 4 of 24Part 10 of 10/Capstone: A Chip-Block Verification Lab/Lesson 4 of 24All levels

    Stage 1 Bridge and the Assumption Contract

    Prove the bridge with every input free, find its three planted faults, and judge a proposed input rule against them.

    Why it matters

    The symptom: a word vanishes under backpressure, yet a review note says the block “proved clean”. A proof is only as strong as the model around it. Before you accept a clean result, list what the proof assumed about the inputs. This lesson gives you the contract of the bridge and one input rule that a colleague proposes “to make the proof faster”; you decide what the rule does to the faults.

    Inputs

    • The bridge below, with the BUG switch (W1 to W3) and the HELPER switch for the three helper assertions.

    • The property set already written: skid-needs-output, conservation, valid hold, data hold, ready independence, tracked-word integrity with its helpers, and five covers.

    • The contract table.

    • Read the BUG == lines of the listing last, after you have read each trace. The listing adds one cover, c_tracked_out, to the four covers of the bridge in the earlier sections; it belongs to this lab only.

    Side

    Rule

    Where it is checked

    Inputs in_valid, in_data, out_ready

    Free in every cycle. The only constraint on the model is the reset at step 0.

    Nothing is assumed beyond the harness

    Outputs out_valid, out_data

    A stalled output keeps its valid and its data in the next cycle.

    a_valid_hold, a_data_hold

    Output in_ready

    A function of the skid register alone, with no path from out_ready.

    a_no_ready_comb

    Counts

    Accepted words minus delivered words equal the words held.

    a_conserve

    Tracked word

    The word delivered as number tn is the word accepted as number tn.

    a_data with its helpers

    hs_bridge.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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    // formal: prove 4 expect pass slang top hs_bridge
    `define FORMAL
    // hs_bridge: valid/ready register stage with a skid register (two words can be held).
    // Output stage ovld/odat plus skid stage svld/sdat. in_ready is a function of a register only.
    // Synchronous active-high reset empties both stages. A transfer happens in a cycle where valid
    // and ready are both 1. BUG selects a variant (0 is the design as specified); HELPER=0 removes the helpers.
    module hs_bridge #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       in_valid,
      output wire       in_ready,
      input  wire [7:0] in_data,
      output wire       out_valid,
      input  wire       out_ready,
      output wire [7:0] out_data
    );
      reg       ovld, svld;
      reg [7:0] odat, sdat;
    
      assign in_ready  = !svld;
      assign out_valid = ovld;
      assign out_data  = odat;
    
      wire in_xfer  = in_valid && in_ready;
      wire out_xfer = ovld && out_ready;
      wire stalled  = ovld && !out_ready;
    
      always @(posedge clk) begin
        if (rst) begin
          ovld <= 1'b0;
          svld <= 1'b0;
        end else if (stalled) begin
          if (in_xfer && BUG != 1) begin svld <= 1'b1; sdat <= in_data; end
          if (BUG == 2 && !in_valid) ovld <= 1'b0;
          if (BUG == 3 && in_xfer)   odat <= in_data;
        end else begin
          if (svld) begin
            odat <= sdat; ovld <= 1'b1; svld <= 1'b0;
          end else begin
            odat <= in_data; ovld <= in_valid;
          end
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- counters: accepted and delivered words (4 bits, wrap at 16) ----
      reg [3:0] acc, dlv;
      always @(posedge clk) begin
        if (rst) begin acc <= 4'd0; dlv <= 4'd0; end
        else begin
          if (in_xfer)  acc <= acc + 4'd1;
          if (out_xfer) dlv <= dlv + 4'd1;
        end
      end
    
      always @(*) if (!init) begin
        a_skid_needs_out: assert (!svld || ovld);
        a_conserve:       assert (acc - dlv == {3'b0, ovld} + {3'b0, svld});
      end
    
      // ---- protocol: history-based, so skip the cycle after reset ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_valid_hold: assert (!($past(out_valid) && !$past(out_ready)) || out_valid);
        a_data_hold:  assert (!($past(out_valid) && !$past(out_ready)) || out_data == $past(out_data));
        a_no_ready_comb: assert (in_ready == !svld);
      end
    
      // ---- data integrity by tracked-word abstraction: follow the n-th accepted word ----
      (* anyconst *) wire [3:0] tn;
      reg [3:0] tn_q;
      always @(posedge clk) tn_q <= tn;
      always @(*) if (!init) assume (tn == tn_q);
      reg       tv;      // the tracked word has been accepted and not yet delivered
      reg [7:0] td;
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (in_xfer && acc == tn) begin tv <= 1'b1; td <= in_data; end
          else if (out_xfer && dlv == tn) tv <= 1'b0;
        end
      end
      always @(*) if (!init && out_xfer && tv && dlv == tn) a_data: assert (out_data == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) begin
          h_in_out:  assert (!(tv && dlv == tn) || (ovld && odat == td));
          h_in_skid: assert (!(tv && dlv + 4'd1 == tn) || (svld && sdat == td));
          h_pos:     assert (!tv || (dlv == tn) || (dlv + 4'd1 == tn));
        end
      end endgenerate
    
      // ---- covers ----
      always @(*) if (!init) begin
        c_stall:     cover (stalled);
        c_skid_used: cover (svld);
        c_backpress_full: cover (!in_ready);
        c_flow:      cover (in_xfer && out_xfer);
        c_tracked_out: cover (out_xfer && tv && dlv == tn);
      end
    `endif
    endmodule
    Free inputs enter the bridge under proof. The outputs carry the asserted promises. A dotted arrow shows a proposed input rule restricting the inputs.
    The contract of the bridge: inputs are free, outputs carry the assertions, and a proposed input rule would restrict the inputs.

    The proposed input rule lives in a wrapper: the upstream never sends while the output is not ready. The block below shows the good design under the rule, which passes; the deliverable is what happens to each variant with the rule on and off.

    hs_bridge.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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    // formal: bmc 12 expect pass slang top bridge_rule_top define BUG=0 define RULE=1
    `define FORMAL
    // hs_bridge: valid/ready register stage with a skid register (two words can be held).
    // Output stage ovld/odat plus skid stage svld/sdat. in_ready is a function of a register only.
    // Synchronous active-high reset empties both stages. A transfer happens in a cycle where valid
    // and ready are both 1. BUG selects a variant (0 is the design as specified); HELPER=0 removes the helpers.
    module hs_bridge #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       in_valid,
      output wire       in_ready,
      input  wire [7:0] in_data,
      output wire       out_valid,
      input  wire       out_ready,
      output wire [7:0] out_data
    );
      reg       ovld, svld;
      reg [7:0] odat, sdat;
    
      assign in_ready  = !svld;
      assign out_valid = ovld;
      assign out_data  = odat;
    
      wire in_xfer  = in_valid && in_ready;
      wire out_xfer = ovld && out_ready;
      wire stalled  = ovld && !out_ready;
    
      always @(posedge clk) begin
        if (rst) begin
          ovld <= 1'b0;
          svld <= 1'b0;
        end else if (stalled) begin
          if (in_xfer && BUG != 1) begin svld <= 1'b1; sdat <= in_data; end
          if (BUG == 2 && !in_valid) ovld <= 1'b0;
          if (BUG == 3 && in_xfer)   odat <= in_data;
        end else begin
          if (svld) begin
            odat <= sdat; ovld <= 1'b1; svld <= 1'b0;
          end else begin
            odat <= in_data; ovld <= in_valid;
          end
        end
      end
    
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- counters: accepted and delivered words (4 bits, wrap at 16) ----
      reg [3:0] acc, dlv;
      always @(posedge clk) begin
        if (rst) begin acc <= 4'd0; dlv <= 4'd0; end
        else begin
          if (in_xfer)  acc <= acc + 4'd1;
          if (out_xfer) dlv <= dlv + 4'd1;
        end
      end
    
      always @(*) if (!init) begin
        a_skid_needs_out: assert (!svld || ovld);
        a_conserve:       assert (acc - dlv == {3'b0, ovld} + {3'b0, svld});
      end
    
      // ---- protocol: history-based, so skip the cycle after reset ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        a_valid_hold: assert (!($past(out_valid) && !$past(out_ready)) || out_valid);
        a_data_hold:  assert (!($past(out_valid) && !$past(out_ready)) || out_data == $past(out_data));
        a_no_ready_comb: assert (in_ready == !svld);
      end
    
      // ---- data integrity by tracked-word abstraction: follow the n-th accepted word ----
      (* anyconst *) wire [3:0] tn;
      reg [3:0] tn_q;
      always @(posedge clk) tn_q <= tn;
      always @(*) if (!init) assume (tn == tn_q);
      reg       tv;      // the tracked word has been accepted and not yet delivered
      reg [7:0] td;
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (in_xfer && acc == tn) begin tv <= 1'b1; td <= in_data; end
          else if (out_xfer && dlv == tn) tv <= 1'b0;
        end
      end
      always @(*) if (!init && out_xfer && tv && dlv == tn) a_data: assert (out_data == td);
      generate if (HELPER) begin : g_helper
        always @(*) if (!init) begin
          h_in_out:  assert (!(tv && dlv == tn) || (ovld && odat == td));
          h_in_skid: assert (!(tv && dlv + 4'd1 == tn) || (svld && sdat == td));
          h_pos:     assert (!tv || (dlv == tn) || (dlv + 4'd1 == tn));
        end
      end endgenerate
    
      // ---- covers ----
      always @(*) if (!init) begin
        c_stall:     cover (stalled);
        c_skid_used: cover (svld);
        c_backpress_full: cover (!in_ready);
        c_flow:      cover (in_xfer && out_xfer);
        c_tracked_out: cover (out_xfer && tv && dlv == tn);
      end
    `endif
    endmodule
    // lesson-local wrapper: the bridge under an extra rule on its input. RULE=0 removes the rule.
    module bridge_rule_top #(parameter BUG = 0, parameter RULE = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       in_valid,
      input  wire [7:0] in_data,
      input  wire       out_ready,
      output wire       in_ready,
      output wire       out_valid,
      output wire [7:0] out_data
    );
      hs_bridge #(.BUG(BUG)) u (.clk(clk), .rst(rst), .in_valid(in_valid),
        .in_ready(in_ready), .in_data(in_data), .out_valid(out_valid),
        .out_ready(out_ready), .out_data(out_data));
    `ifdef FORMAL
      reg t_init = 1'b1;
      always @(posedge clk) t_init <= 1'b0;
      always @(*) if (t_init) assume (rst);
      // the rule: the upstream never sends while the output is not ready
      always @(*) if (RULE && !t_init) assume (!in_valid || out_ready);
    `endif
    endmodule

    Run the variants

    Edit the header of the first block (plain bridge) for W1 to W3, and the header of the second block (wrapper) for the rule: define RULE=1 and define RULE=0.

    Variant

    Edit the header of the block above to

    Expected category

    W1

    // formal: bmc 12 expect fail slang top hs_bridge define BUG=1

    counterexample

    W2

    // formal: bmc 12 expect fail slang top hs_bridge define BUG=2

    counterexample

    W3

    // formal: bmc 12 expect fail slang top hs_bridge define BUG=3

    counterexample

    Deliverable

    Produce

    How

    The failing property and step for each variant, without the rule

    Plain bridge, depth 12, then lower the depth.

    A table of variant against rule on/off

    Wrapper with RULE=1 and RULE=0: counterexample or pass at depth 12.

    A verdict on the rule

    Which variants does it hide, and what does that make the rule?

    Four rows

    Conservation, hold rules, tracked-word integrity, and the covers; the register entries each one needs.

    Pass criterion

    • The good design is proved; each variant gives a counterexample on the plain bridge.

    • Your table shows which variants stay visible with the rule on. A rule under which a variant passes is not filed as evidence.

    • The package check passes for your rows.

    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
    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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    # ---- formal lab results ledger, engine v2: every row cites a typed run (kind, depth, outcome[, design]) ----
    STATUS = ("proven", "bounded", "cex", "cover-reached", "inconclusive")
    FROM_RUN = {"proven": {("prove", "proved")},
                "bounded": {("bmc", "bounded")},
                "cex": {("bmc", "counterexample"), ("prove", "counterexample")},
                "cover-reached": {("cover", "reached")},
                "inconclusive": {("prove", "unproven"), ("cover", "not-reached")}}
    NEED = {"sync_fifo": 9, "rr_arbiter": 5, "hs_bridge": 4, "csr_block": 3,
            "pipe3": 5, "pipe3_eq": 5, "mod10_counter": 15, "toggle_demo": 4}
    KEYS = ("property", "design", "status", "bound", "why", "run", "assumes", "cover", "kills", "rev")
    REV = 3
    ALWAYS = "reset-at-step-0"
    TRACKED = "tracked-constant-stable"
    def row_problems(e, runs, tracked):
        n = e.get("property", "?")
        miss = [k for k in KEYS if k not in e]
        if miss:
            return ["missing-field:%s:%s" % (n, ",".join(miss))]
        if e["status"] not in STATUS:
            return ["bad-status:" + n]
        p = []
        if e["rev"] != REV:
            p.append("stale:" + n)
        if e["run"] not in runs:
            return p + ["no-such-run:" + n]
        kind, depth, outcome = runs[e["run"]][:3]
        if len(runs[e["run"]]) > 3 and runs[e["run"]][3] != e["design"]:
            p.append("run-of-another-design:" + n)         # a run typed with its design may be cited only by that design's rows
        if (kind, outcome) not in FROM_RUN[e["status"]]:
            p.append("status-not-from-run:" + n)
        if e["bound"] != depth:
            p.append("bound-not-run-depth:" + n)
        has_cover = bool(str(e["cover"]).strip())
        if e["status"] in ("proven", "bounded") and not (has_cover and e["kills"]):
            p.append("no-evidence:" + n)
        if e["status"] == "cover-reached" and not has_cover:
            p.append("no-evidence:" + n)
        if e["status"] == "cex" and not e["kills"]:
            p.append("no-evidence:" + n)
        if e["status"] == "bounded":
            if e["design"] not in NEED:
                p.append("unknown-design:" + n)
            elif depth < NEED[e["design"]]:
                p.append("short-bound:" + n)
            if len(str(e["why"]).strip()) < 12:
                p.append("unjustified:" + n)
        if ALWAYS not in e["assumes"]:
            p.append("hidden-assume:%s:%s" % (n, ALWAYS))
        if n in tracked and TRACKED not in e["assumes"]:
            p.append("hidden-assume:%s:%s" % (n, TRACKED))
        return p
    def ledger_ok(ledger, runs, required, mutants, register, tracked, final=True):
        names = [e.get("property") for e in ledger]
        bad = ["duplicate:" + n for n in sorted({n for n in names if names.count(n) > 1})]
        bad += ["missing-row:" + r for r in sorted(set(required) - set(names))]
        bad += [q for e in ledger for q in row_problems(e, runs, tracked)]
        good = [e for e in ledger if not row_problems(e, runs, tracked)]      # only a row without complaints counts as evidence
        solid = {e["property"] for e in good if e["status"] in ("proven", "cover-reached")}
        for a, rows in sorted(register.items()):
            if rows is None:
                if final:
                    bad.append("deferred-assume:" + a)      # a stage may defer a discharge; a final package may not
            elif not (rows and all(r in solid for r in rows)):
                bad.append("open-assume:" + a)
        bad += ["unregistered-assume:%s:%s" % (e.get("property"), a) for e in ledger
                for a in e.get("assumes", []) if a not in register]
        killed = {m for e in good if e["status"] == "cex" for m in e["kills"]}
        bad += ["survivor:" + m for m in sorted(set(mutants) - killed)]
        bad += ["unsupported-kill:%s:%s" % (e.get("property"), m) for e in ledger
                if e.get("status") in ("proven", "bounded") for m in e.get("kills", []) if m not in killed]
        return bad
    def mutation_score(ledger, mutants):
        assert mutants, "no mutants registered"
        killed = {m for e in ledger if e.get("status") == "cex" for m in e.get("kills", [])}
        return len(killed & set(mutants)) / len(mutants)
    
    RUNS = {
        "bridge-prove": ("prove", 4, "proved"),
        "bridge-rule-base": ("bmc", 12, "bounded"),
    }
    REQUIRED = ["bridge-conservation", "bridge-hold-rules", "bridge-data-integrity"]
    MUTANTS = ["bridge-W%d" % i for i in range(1, 4)]
    REGISTER = {"reset-at-step-0": None, "tracked-constant-stable": None}   # discharged by later rows
    TRACKED_ROWS = {"bridge-data-integrity"}
    YOUR_ROWS = []          # fill from your own runs
    bad = ledger_ok(YOUR_ROWS, RUNS, REQUIRED, MUTANTS, REGISTER, TRACKED_ROWS, final=False)
    if YOUR_ROWS:
        assert bad == [], bad
        verdict = "stage 1 bridge package consistent"
    else:
        expected = ["missing-row:" + r for r in REQUIRED] + ["survivor:" + m for m in MUTANTS]
        assert sorted(bad) == sorted(expected), bad
        verdict = "the empty package is rejected with %d complaints" % len(bad)
    # an input rule is an assumption: it must be registered, and a row that uses it must say so
    rule_row = dict(property="bridge-under-rule", design="hs_bridge", status="bounded", bound=12, why="rule on: no input while stalled",
                    run="bridge-rule-base", assumes=["reset-at-step-0", "no-input-while-stalled"], cover="c_stall step 2",
                    kills=[], rev=3)
    bad_rule = ledger_ok([rule_row], RUNS, [], [], REGISTER, set(), final=False)
    assert "unregistered-assume:bridge-under-rule:no-input-while-stalled" in bad_rule, bad_rule
    assert "no-evidence:bridge-under-rule" in bad_rule
    print("PASS: %s; a row that leans on an unregistered input rule is refused" % verdict)

    Hint ladder

    • Hint 1: for each variant, start from the property family that matches its symptom word: loss is counted, a changed word is a value, a dropped valid is a protocol rule.

    • Hint 2: run the variant at depth 12, lower the depth to find the failing step, and write the trace as a table of inputs and state.

    • Hint 3: for the rule, ask which inputs the rule forbids and whether any variant needs those inputs to show itself.

    • Hint 4: remove the rule again and rerun; the verdict of the rule is the difference between the two runs, not the design.

    Verify and debug

    When a proof passes suspiciously early with a variant selected, list the assumptions before reading the design: the fastest pass in the lab is a model with nothing left to check. Replay a counterexample against the unconstrained design before trusting a fix, and confirm the fix by watching the same step pass on the corrected design.

    An assume that hides a real fault: the block proves clean and silicon fails. Symptom is a proof that survives a planted fault. Cause is an assumption that deletes the failing input, instead of an assertion that the design tolerates it. Fix: assert at the boundary what the neighbour owes, and review every assume for the behaviour it deletes. Found by rerunning the proof with each assume removed in turn.

    “Assume versus assert: how do you decide?” Answer: assume on primary inputs only the rules the neighbour provably keeps; assert on outputs everything you owe. Every assume is an assert in the neighbour or at integration. Then say what this lab shows: with every input free, nothing is owed by anyone.

    Check yourself A proof passes with a rule on an input and fails with the rule off, for the same variant. What did the two runs compare?

    Two models, not two designs. Which behaviours the rule removed, and whether you accept that, is the verdict the deliverable asks for; the Solutions topic gives the reference verdict.

    Check yourself Which assumptions does the bridge proof actually use?

    The reset at step 0 and, for the tracked-word property, the free constant tn assumed equal to its one-cycle-old copy. Every input is free.

    Related topics

    • Next: Stage 1 Covers and Mutants

    • Earlier: Assumptions as a Contract

    • After you have filed your rows: Solutions, Bridge

    Previous

    163 / 183

    Next: Stage 1 Covers and Mutants

    Up next

    Up nextStage 1 Covers and Mutants

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error