This capstone verifies a FIFO completely: flags against a reference count, data order with a tracked value, covers, and a full proof by induction, with the helper invariants that the proof needs. It brings together the techniques of Chapters 3 to 8 on one design. The FIFO is parameterized; the proof uses 4 entries of 8 bits and holds for the logic of any size written the same way.
12 min read | Intermediate
What You'll Learn
- Assemble a complete property set for a FIFO from its specification
- Run a bounded check with covers as the first milestone
- Strengthen the properties round by round until the induction step passes
- Run both halves of the proof in ChipVerify Lab
The Design
The FIFO from the synchronous FIFOs article, with an extra wrap bit on each pointer and two parameters, data width W and address width AW:
module fifo_p #(parameter W = 8, parameter AW = 2) (input clk, input rst, input push, input [W-1:0] din, input pop,
output [W-1:0] dout, output full, output empty);
reg [W-1:0] mem [0:(1<Milestone 1: The Property Set
- Reference count
n, updated from accepted pushes and pops. - Flags:
fullexactly whennis the depth,emptyexactly when it is 0. - Order: a tracked word
magiccomes out when it reaches the head. - Covers: full, empty after full, push and pop in the same cycle while full, magic leaving the FIFO.
No assumptions are needed: push, pop and din are free, because the FIFO itself must reject a push when full and a pop when empty. At depth 10, all properties pass and the covers are reached, full by step 5 and magic leaving by step 2. That is a solid bounded result. It is not yet a proof.
Milestone 2: The Proof
Prove mode returns UNKNOWN. Strengthening proceeds in rounds, as in the strengthening invariants article:
- Count and pointers. The induction trace starts with a reference count that does not match the pointers. Helper:
n == wptr - rptr, computed inAW+ 1 bits so that the subtraction wraps like the pointers. Still UNKNOWN. - Where the tracked word is. The next trace starts with
magicmarked as tracked although it is not in the FIFO at all: the FIFO is empty. Helper: while tracking,aheadis less thann, and the memory atrptr + aheadholdsmagic. Still UNKNOWN. - Range. The next trace starts with
n= 5, one more than the depth, with pointers that agree with it modulo 8. Helper:nis at most the depth. Now the proof passes with k = 1.
SBY [run] summary: engine_0 (smtbmc bitwuzla) returned pass for basecase SBY [run] summary: engine_0 (smtbmc bitwuzla) returned pass for induction SBY [run] summary: successful proof by k-induction.
The PDR engine proves the original properties without any helper. The helpers are still worth keeping: they document exactly how the FIFO works, and they make the proof fast and robust when the design changes.
Quick Check
Q: Why does the count helper compute wptr - rptr in AW + 1 bits?
Show Answer
The pointers have AW + 1 bits and wrap at that width. In a wider expression, a write pointer that has wrapped below the read pointer gives a large negative difference instead of the true count. A first version of the helper made exactly this mistake, and failed even the base case at step 9.
Running It in ChipVerify Lab
The example collects the properties and helpers into single signals, so that both halves of the proof use exactly the same set:
// the properties
wire p_flags = (full == (n == (1 << AW))) && (empty == (n == 0));
wire p_order = !(tracking && ahead == 0 && do_pop) || dout == magic;
// the helpers that make them inductive
wire [AW:0] diff = wptr - rptr;
wire [AW-1:0] mpos = rptr[AW-1:0] + ahead[AW-1:0];
wire h_count = (n == diff) && (n <= (1 << AW));
wire h_magic = !tracking || (ahead < n && mem[mpos] == magic);
wire all_ok = p_flags && p_order && h_count && h_magic;
`ifndef INDUCTION_STEP
// base case: from reset
reg init = 1'b1;
always @(posedge clk) init <= 1'b0;
always @(*) if (init) assume (rst);
always @(*) if (!init && !rst) assert (all_ok);
`else
// induction step, K = 1: start anywhere, all properties hold in step 0, check step 1
always @(*) assume (!rst);
reg [7:0] step = 8'd0;
always @(posedge clk) step <= step + 8'd1;
always @(*) if (step < 8'd1) assume (all_ok);
always @(*) if (step == 8'd1) assert (all_ok);
`endif
Run the base case at depth 10, then uncomment the `define INDUCTION_STEP line and run the step at depth 2. Both pass: the FIFO is proven. Remove any one helper from all_ok and the step fails at step 1.
One trap is worth knowing. The reference count and the tracking registers in the example have no initial values. If they had, such as reg [AW+1:0] n = 0;, the formal tool would start the induction step with n = 0 and tracking = 0, which is not "anywhere" at all, and the step would pass even without helpers. Formal-only registers that take part in the step must start free.
Quick Check
Q: The base case and the step pass for 4 entries. Does that prove a 64-entry FIFO built from the same code?
Show Answer
Not formally: a proof is for the configuration checked. But here it is cheap to check the real size. With the helpers, the induction proof does not depend on how many cycles it takes to fill the FIFO, and on the lab engine it passes for 64 entries of 32 bits in under a second, while a bounded check of 8 entries already times out.
Common Mistakes
Mistake #1: Stopping at the bounded result
For a block this small, a full proof is within reach. Bounded is a milestone, not the goal.
Mistake #2: Initial values on formal-only registers in the induction step
An initializer fixes the start value and silently turns the step into a check from a known state. Leave them free.
Mistake #3: Helpers with the wrong width
Arithmetic on pointers must wrap at the pointer width. Size helper expressions explicitly.
Practice Exercise
Challenge: Prove the FIFO
Open the example. Run the base case at depth 10 and the step at depth 2. Then remove the helpers from all_ok one at a time, run the step each time, and read the start state of each counterexample.
Hint
Look at n, wptr, rptr, tracking, ahead and the memory slot mem[mpos] in step 0 of each trace.
Solution
With all helpers, both halves pass. Without h_magic, the step starts with magic marked as tracked while the FIFO is empty, and the order check fails at step 1 when a word that is not magic comes out. Without the range part of h_count, it starts with n = 7, which matches the pointer difference modulo 8 but is more than the depth, and the flag check fails. Without both helpers, the step again starts from a tracked word that is not in the FIFO. Each helper rules out one family of unreachable start states.
Open the ready-made example in the lab and run the formal check from the formal panel. The free tier allows a depth of up to 10 cycles, and PRO allows up to 100.
Summary
- A complete FIFO check: reference count, flags, tracked-value order and covers, with no assumptions on the inputs.
- The bounded check passed at depth 10 with every cover reached.
- Three helper invariants, count equals pointer difference, count within range, and the tracked word in its slot, made the proof pass with k = 1.
- Both halves of the proof run in the lab with a
`defineswitch. - Formal-only registers in the induction step must have no initial values.