SEC: Sequential Equivalence
Prove a pipelined block equal to its reference when the flops no longer pair, with the latency written as one line of modelling code.
- Lesson
- F6.2
- Time
- 20 min
- Runs on
- SymbiYosys
- Check
- 5 questions
A designer pipelined an adder to meet timing, and LEC refused to pair the new flops. "Same adder, two cycles later" went into a waiver. It was true except when valid dropped for one cycle: the result already in the pipe was lost.
Measured, smtbmc yices, depth 20: FAIL at reported step 4. Stage 2 waited for an input valid that never came, so it kept its old value.
Flops with No Partner
Read the diagram as text
- Shared inputs: valid, a, b
- Gold: add_comb: sum = a + b, same cycle
- Gate: add_pipe2: stage 1: low bytes; stage 2: high bytes
- Modelling code: gold_q1, gold_q2: {valid, sum}, one and two cycles back
- Compare when the gate is valid: pipe_sum == gold_q2.sum
- Shared inputs → Gold: add_comb
- Shared inputs → Gate: add_pipe2
- Gold: add_comb → Modelling code: gold_q1, gold_q2
- Modelling code: gold_q1, gold_q2 → Compare when the gate is valid
- Gate: add_pipe2 → Compare when the gate is valid
Nothing pairs flop to flop, so LEC has nothing to cut at. The question becomes a property over time.
Sequential equivalence drops the matched-state assumption. Gold and gate may have different flops, encodings or pipeline depths. The question is now: from reset, for every input sequence, are the outputs equal at every step, or equal after a stated latency?
That is a safety property over time, so it needs the full machinery: BMC for bugs, induction or PDR for the proof.
The Latency Is Part of the Specification
assert property (@(posedge clk) disable iff (rst)
pipe_valid |-> pipe_sum == $past(gold_sum, 2)); always @(posedge clk) begin
if ($initstate) begin assume(rst); end else begin assume(!rst); end
end
// modelling code: the gold's {valid, sum}, one and two cycles back
localparam LAT = 2;
reg [17:0] gold_q1 = 0, gold_q2 = 0;
always @(posedge clk) begin
gold_q1 <= rst ? 18'd0 : {gold_valid, gold_sum};
gold_q2 <= rst ? 18'd0 : gold_q1;
end
wire [17:0] gold_d = LAT == 1 ? gold_q1 : gold_q2;
always @(posedge clk) if (!rst) begin
assert(pipe_valid == gold_d[17]);
if (pipe_valid) assert(pipe_sum == gold_d[16:0]);
endThe gate's output at step t equals the gold's at step t − 2. Write the latency as 1 and the proof fails at the first valid result: FAIL at reported step 3. Nothing in the design is wrong; the specification was.
Bugs, Then the Proof
| Run | Task | Result |
|---|---|---|
| Fixed pipeline, latency 2 | bmc, depth 20 | BMC PASS to depth 20 |
BUG_STALE_STAGE, latency 2 | bmc, depth 20 | FAIL at reported step 4 |
| Fixed pipeline, latency 1 | bmc, depth 20 | FAIL at reported step 3 |
| Fixed pipeline, latency 2 | prove, k = 2 | UNKNOWN at k = 2 (base case passed, induction step failed) |
| Fixed pipeline, latency 2 | prove, k = 3 | Prove PASS (k = 3) |
| Fixed pipeline, latency 2 | abc pdr | Prove PASS (abc pdr), 2.2 s |
BUG_STALE_STAGE | prove, k = 3 | UNKNOWN at k = 3 (base case passed, induction step failed) |
Measured with the site's runner. The last row is a buggy design: a base case too short to reach the bug gives UNKNOWN, never PASS.
BMC found the stale stage at reported step 4. On the fixed pipeline, k-induction (F2.4) fails at k = 2 and closes at k = 3: the latency plus one. With three steps in the window, every pipeline register has been reloaded from inputs the step can see. PDR finds the same proof with no k.
Notice what the miter checks without being told: valid itself must arrive on time, so a stage that drops or doubles a result fails too. If induction ever needs help on a deeper pipeline, the helper is "stage register equals the delayed gold operand" (F4.6).
What Sequential Equivalence Covers
| Change to the design | Combinational LEC | Sequential equivalence |
|---|---|---|
| Gate sizing, buffering, logic restructuring | yes | yes |
| Retiming across flops | no: flops move | yes |
| FSM re-encoding | no: different state bits | yes |
| Added pipeline stages | no: extra flops | yes, with the latency stated |
| Clock gating | only with tool support | yes, outputs equal when enabled |
Commercial tools sell SEC as its own product; the bench above is the same idea on an open property checker.
EQY can also try this pair. With no flops to match it compares outputs in the same cycle, and the runner reports UNKNOWN: equivalence not proven. That is the honest result for a pipeline: EQY's partitions assume the outputs line up in time, and here they do not.
Asked in Interviews
| Question | Answer |
|---|---|
| What breaks LEC's flop matching? | Retiming, pipelining, clock gating, FSM re-encoding: any change to the state elements. |
| How do you set up SEC? | A miter on shared inputs, the latency as modelling code, the equality gated by the gate's valid, proven with induction or PDR from reset. |
| What does "equivalent under a latency" mean? | The same outputs for every input sequence, shifted by the stated number of cycles. |
Try It
mode prove, depth 3.[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top sec_top
[files]
design.svFind the bubble
Run as loaded: FAIL at reported step 4. Read valid in the trace and name the cycle the operation entered.
Fix the stage
In add_pipe2, make stage 2 load on
v1, the stage-1 valid. Run bmc again.Prove it
Switch to
prove. Try depth 2, then 3: UNKNOWN at k = 2 (base case passed, induction step failed), then Prove PASS (k = 3).Break the specification
Set
LATto 1: FAIL at reported step 3.
module adder(input clk, rst, valid, input [15:0] a, b, output [16:0] sum, output valid_out);
assign sum = a + b;
assign valid_out = valid;
endmodule
module adder(input clk, rst, valid, input [15:0] a, b,
output reg [16:0] sum = 0, output reg valid_out = 0);
reg [8:0] lo1 = 0; // stage 1: low bytes added, carry in bit 8
reg [7:0] ah1 = 0, bh1 = 0; // stage 1: high bytes, not yet added
reg v1 = 0;
wire [8:0] hi = ah1 + bh1 + lo1[8];
always @(posedge clk) begin
if (rst) begin v1 <= 0; valid_out <= 0; end
else begin
v1 <= valid;
valid_out <= v1;
if (valid) begin lo1 <= a[7:0] + b[7:0]; ah1 <= a[15:8]; bh1 <= b[15:8]; end
if (v1) sum <= {hi, lo1[7:0]};
end
end
endmodule
design.sv
props.sv
Use assert property (@(posedge clk) ...);. Leave blank for immediate assertions in the design.
Inputs are free unless constrained. Run to find a counterexample or check a proof.
Model: one positive-edge clock, binary data. Sampled history requires declaration-initialized variables; primary-input history is unsupported.
SBY summary and log
Check Yourself
Remember
- Sequential equivalence lets the flops differ and asks for equal outputs over time, from reset.
- The latency is modelling code and part of the specification; wrong by one, the proof fails.
- BMC finds pipeline bugs; k-induction closes at the latency plus one, or PDR finds the invariant.
- Never assume valid away: bubbles are where pipelines lose data.