Capstone: End-to-End Proof of a Packet Router
Prove a two-by-two router end to end with one symbolic packet, catch a reordering queue, and see a fixed-priority arbiter pass every safety check and fail the bounded wait.
- Lesson
- F9.2
- Time
- 40 min
- Runs on
- SymbiYosys
- Check
- 4 questions
A two-by-two router used fixed priority at each output. When both inputs had a packet for the same output, input 0 always won. Per-source order held, so the simulation scoreboard never fired. The spec had a second sentence, "no offer waits more than one round", and nobody had written it as a property.
One Packet Carries the Proof
Read the diagram as text
- input 0: tvalid, tdata, tdest
- input 1: tvalid, tdata, tdest
- grant per output: round-robin when both want it
- queue 0: two entries {tid, tdata}
- queue 1: two entries {tid, tdata}
- output 0: tvalid, tdata, tid
- output 1: tvalid, tdata, tid
- input 0 → grant per output: 1 accepted
- input 1 → grant per output
- grant per output → queue 0: 2 queued
- grant per output → queue 1
- queue 0 → output 0: 3 its turn, 4 gone
- queue 1 → output 1
42 flop bits of router, 99 with the bench. tid names the input a packet came from (IHI 0051B 2.7).
| Sentence of the spec | What the bench remembers | Check |
|---|---|---|
| Every accepted packet is still inside until it leaves | accepted? (f_in), packets of its flow ahead (f_ahead) | a_inside: its queue holds more than f_ahead |
| Packets of one source to one output leave in order, unchanged | its source (f_src), output (f_dest), payload (f_data) | a_turn: at its turn, the packet leaving is it |
| Nothing leaves that was not sent there: no duplicate, no wrong output | packets owed per flow (fl00 .. fl11) | a_owed0, a_owed1 |
Four checks, one tracked packet. The solver picks its source once and the moment it is accepted, so every packet of every stream is the tracked one in some trace.
End to end means: what enters leaves once, at the right output, unchanged, in order within its flow. The bench never stores the stream. It follows one packet the solver picks, as F4.4 did for a FIFO, and counts the packets of its flow still ahead of it.
The tag is the moment of acceptance, never the payload. Payloads repeat; a tracker that follows "the first packet with data 5A" follows the wrong one as soon as two packets carry 5A.
The Proof
| Run | Result |
|---|---|
| Covers: a packet each way, both inputs at one output, a full queue, the tracked packet gone | Cover PASS, last cover reached at step 4, 0.6 s; deepest c_full |
Safety set, abc pdr | Prove PASS (abc pdr), 0.8 s |
Safety set, aiger rIC3 | Prove PASS (rIC3), 0.6 s |
| Safety set, k-induction, k = 8 | UNKNOWN at k = 8 (base case passed, induction step failed); a_owed0 |
| Safety set with five queue invariants, k = 8 | Prove PASS (k = 8), 0.6 s |
Reset assumed in step 0; the inputs keep TVALID and the payload until accepted (IHI 0051B 2.2). Nothing restricts the outputs' tready.
IC3 finds the invariant on its own. k-induction needs it written down: the queues agree with the flow counts, and the tracked packet sits where f_ahead says (F4.6).
A Queue That Reorders
Measured, bmc depth 20: FAIL at reported step 4 on a_turn.
With two entries from the same input, the faulty queue sends the newer first. At the tracked packet's turn the packet leaving carries other data, and a_turn fails at reported step 4.
The Sentence Safety Cannot Say
Read the diagram as text
- reset: step 0
- input 1 offers: both want output 0
- input 0 wins: a packet queued
- output 0 ready: fairness is kept
- reset → input 1 offers
- input 1 offers → input 0 wins
- input 0 wins → output 0 ready
- output 0 ready → input 0 wins: forever
Drawn, not measured: this site's engine does not yet run liveness jobs. The bounded wait below is what ships.
"Every offer is eventually taken" is liveness. It needs a fairness assumption: each output's tready is high infinitely often. Without it the downstream may stall forever and nothing is owed (F3.7).
With fixed priority the safety set still passes: Prove PASS (abc pdr), 0.8 s. Nothing is dropped, duplicated or reordered. The bug is a wait with no end, and only a liveness or bounded-wait property sees it.
The Bounded Wait That Ships
Measured, bmc depth 20: FAIL at reported step 8 on a_wait. 4 input-0 packets are accepted while input 1 waits.
The stand-in: if tready is never low more than two cycles in a row, every offer is accepted within 5 cycles. The bound is measured: a cover reaches a wait of 5 but not 6, and the proof of the bound passes (Prove PASS (abc pdr), 0.5 s). Fixed priority fails it.
The assumption is named as a stand-in for fairness. A reviewer accepts it, or asks for the liveness run.
Asked in Interviews
| Question | Answer |
|---|---|
| How do you prove a router end to end? | One symbolic packet: picked at acceptance, counted through its flow, checked unchanged at its turn, plus a check that nothing leaves unowed. |
| What does a safety set miss? | Starvation. A fixed-priority arbiter drops and reorders nothing, so every safety check passes; "every offer is eventually taken" fails. |
| How would you ship it today? | A bounded wait under a named stand-in for fairness, with the bound from a measured cover. |
Try It
The lab is the router with the tracker: Prove PASS (rIC3), 0.6 s. The twins add the covers, the invariants for k-induction, the reordering queue and the bounded wait on both arbiters.
[options]
mode prove
[engines]
aiger rIC3
[script]
read_verilog -formal -sv design.sv
prep -top rtr22
[files]
design.svThe checks as SVA
assert property (@(posedge clk) disable iff (rst)
f_in |-> f_room > f_ahead);
assert property (@(posedge clk) disable iff (rst)
f_in && f_lv && f_ahead == 0 |-> f_out == f_data);
No drop; order and payload at its turn.
The liveness obligation (not run here)
assume property (@(posedge clk) s_eventually m0_tready);
assume property (@(posedge clk) s_eventually m1_tready);
assert property (@(posedge clk)
s1_tvalid |-> s_eventually s1_tready);
One obligation per live run, and a companion s_eventually 1'b0 that must fail.
Write the tracker
From the table: what must the bench remember when the packet is accepted, and when it leaves?
Covers
Run the covers; the tracked packet leaves at step 4.
Prove
Run rIC3, then k-induction with and without the invariants.
The twins
Run the reordering queue, then the bounded wait on both arbiters.
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
- End to end: one symbolic packet, tagged by the moment of acceptance, counted through its flow.
- Order and payload are one check at its turn; an owed-count check catches duplicates and wrong outputs.
- A fixed-priority arbiter passes every safety check; starvation is liveness.
- Ship a bounded wait under a named stand-in for fairness, with the bound from a measured cover.