From RTL to a Transition System
See what a formal tool builds from your RTL: state, free inputs, a next-state function and bad states, written as SMT-LIB2, AIGER or BTOR2.
- Lesson
- F2.1
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 5 questions
A team blackboxed a memory to make a proof faster, and the proof finished in seconds. The memory outputs had become free inputs, so the data-path property was proven for a memory that could return anything. One line of the model statistics said so: 43 inputs, where the RTL had 11.
The Box Every Prover Sees
Read the diagram as text
- Inputs I: rst, en: nothing drives them
- Initial states: reset assumed in step 0
- Next-state function f: the logic in front of each flop
- State S: t0, t1 and the property history flops
- Bad states B: states where an assert is false
- Inputs I → Next-state function f: free values
- Next-state function f → State S: S' = f(S, I)
- Initial states → State S: step 0
- State S → Bad states B: assert reads S
Clock, processes and module boundaries are gone. A transition system has state, inputs, one step function and a set of states to avoid.
A prover does not see modules, processes or a clock. It sees state bits (the flops), inputs nobody drives, one function that computes the next state, the allowed first states and the bad states. That object is a transition system.
The rule in concurrent SVA
assert property (@(posedge clk) disable iff (rst)
t1 |-> $past(t0) || $past(t1));
t1 may be set only after t0 was set, or if t1 was already set.
What the engine runs
// t1 |-> $past(t0) || $past(t1), with its history kept in flops
reg f_past_valid=0, t0_q=0, t1_q=0;
always @(posedge clk) begin
f_past_valid <= 1; t0_q <= t0; t1_q <= t1;
if (f_past_valid && !rst && t1) assert(t0_q || t1_q);
end
The history becomes three flops. They are state too.
From design.sv to a Solver File
Read the diagram as text
- design.sv, read and prepared: read_verilog -formal, then prep: flatten, map memories, turn checks into cells
- write_smt2: .smt2: word level
- write_aiger: .aig: bit level, AND gates and latches
- write_btor: .btor: word level
- smtbmc: Yices, Boolector, Bitwuzla, Z3
- abc and aiger engines: bmc3, pdr, avy, rIC3, suprove
- btor engines: btormc, pono
- design.sv, read and prepared → write_smt2
- design.sv, read and prepared → write_aiger
- design.sv, read and prepared → write_btor
- write_smt2 → smtbmc
- write_aiger → abc and aiger engines
- write_btor → btor engines
A mode prep task stops after the first box. Measured: its model/ folder holds 7 files, none of them .smt2, .aig or .btor.
Each engine reads its own file. smtbmc reads SMT-LIB2, where a 4-bit add stays one operation. ABC and the aiger engines read AIGER, where everything is AND gates, inverters and latches. btormc and pono read BTOR2, which keeps words like SMT-LIB2.
[options]
mode bmc
depth 5
[engines]
smtbmc yices
abc bmc3
btor btormc
[script]
read_verilog -formal -sv design.sv
prep -top tog2
[files]
design.sv
Read the AIGER Header
write_aiger -ascii, then the symbol map. SBY writes the same file in binary.aag 33 3 8 0 22 1 3 0 0
input 0 0 clk
input 1 0 rst
input 2 0 en
latch 0 0 t0
latch 1 0 t1
latch 2 0 f_past_valid
latch 3 0 t0_q
latch 4 0 t1_q| Field | tog2 | Meaning |
|---|---|---|
| M | 33 | largest variable index |
| I | 3 | inputs: clk, rst, en |
| L | 8 | latches: the state bits |
| O | 0 | outputs (the flow removes them) |
| A | 22 | AND gates: the size of f |
| B | 1 | bad-state properties: one assert |
| C | 3 | constraints: the assumptions |
L and A are the first two numbers a formal engineer reads: how much state, how much logic.
| Latches | Count | Why they exist |
|---|---|---|
| t0, t1 | 2 | the design flops |
| f_past_valid, t0_q, t1_q | 3 | the history the property needs ($past, written as flops) |
| two unnamed flops | 2 | they sample the clocked assertion at the edge |
| one more latch | 1 | $initstate: on in the first step only |
Together: 8. Two flops sample the assertion, so a failure becomes visible one step after the sampled cycle.
I counts clk, but every latch now steps once per frame, so the clock drives nothing. C counts the two reset assumptions and one more that the flow adds to pin clk.
Words and Bits
add per counter. The same design in AIGER has A = 81 AND gates.30 sort bitvec 4
31 const 30 0000
32 state 30 a
33 init 30 32 31
43 uext 30 6 3
44 add 30 32 43
45 ite 30 3 44 32
46 ite 30 4 31 45
47 next 30 32 46
17 bad 16 _witness_.check_design_sv_10_assert_11BTOR2 and SMT-LIB2 keep a + 1 as one operation on a 4-bit word. AIGER has already turned it into gates. F2.2 shows why both exist: word-level solvers rewrite arithmetic, bit-level engines such as PDR need gates.
The .smt2 file has the box as named functions: tog2_i is the initial-state test, tog2_t the transition, tog2_a the assertions and tog2_u the assumptions.
Add a Flop Nobody Reads
| Model | tog2 | tog3 (one more flop) |
|---|---|---|
$dff cells after prep | 5 | 6 |
| SMT2 registers | 7 | 8 |
BTOR2 state lines | 8 | 9 |
| AIGER header | aag 33 3 8 0 22 1 3 0 0 | aag 33 3 8 0 22 1 3 0 0 |
Measured. The AIGER flow removed t2 entirely.
The new flop t2 feeds no assertion, so it is outside the cone of influence: the state and inputs a property can depend on. The AIGER flow optimized it away. The other two models keep it and leave the pruning to the solver. F4.2 uses this rule to shrink real proofs.
Asked in Interviews
| Question | Answer |
|---|---|
| How does a tool turn my RTL into something a solver reads? | Flops become state bits, the logic in front of them becomes the next-state function, undriven ports become free inputs and assertions become bad states. It is written as SMT-LIB2, AIGER or BTOR2. |
| What is AIGER? | The bit-level format of the hardware model checking competition: inputs, latches, AND gates, bad-state properties and constraints. |
| What is a cone of influence? | The state and inputs a property depends on. Everything outside it can be dropped for that property. |
Try It
The lab runs tog2 in bmc mode at depth 5 with smtbmc yices. Measured: PASS, 500 ms on this machine.
Run it
Read the verdict and the SBY summary. A bounded PASS at depth 5 covers samples 0 to 3.
Add a third flop
Rename the module and top to
tog3, addt2that toggles whent0 && t1 && en, and re-run. The verdict does not change: no assertion readst2.Break the cone
Delete
if (t0)sot1toggles onenalone, and re-run. The solver now reaches a bad state; read the step it reports.
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
- A prover sees state, free inputs, one next-state function, initial states and bad states.
- SMT-LIB2 and BTOR2 keep words; AIGER is gates and latches.
- Latch and AND counts tell you how big a proof is; the input count tells you what is free.
- Logic outside a property’s cone of influence can be dropped for that property.