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 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.
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:
`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
`endifIn 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.
`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
`endifThe 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.
// 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
endmoduleThe 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:
// 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
endmoduleA bounded check to depth 7 for each bug (bug-zoo.mjs --formal 7):
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 bugsThen the full proof for each bug (--formal 4 --induction):
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 bugsHow 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.
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.
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).
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) |
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).
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.
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.
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.