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

The Box Every Prover SeesInputs and the current state feed one block of logic. It computes the next state. Bad states are where an assertion is false.
The box every prover seesInputs and the current state feed one block of logic. It computes the next state. Bad states are where an assertion is false. inputs Inputs I rst, en: nothingdrives them initial Initial states reset assumed in step0 logic Next-statefunction f the logic in front ofeach flop state State S t0, t1 and theproperty historyflops property Bad states B states where anassert is false free valuesS' = f(S, I)step 0assert reads S
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

From design.sv to a Solver FileSBY runs your script once, then writes one model per engine family.
From design.sv to a solver fileSBY runs your script once, then writes one model per engine family. front end design.sv, readand prepared read_verilog -formal,then prep: flatten,map memories, turnchecks into cells writer write_smt2 .smt2: word level writer write_aiger .aig: bit level, ANDgates and latches writer write_btor .btor: word level engine smtbmc Yices, Boolector,Bitwuzla, Z3 engine abc and aigerengines bmc3, pdr, avy, rIC3,suprove engine btor engines btormc, pono
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.

With three engines listed, the measured model/ folder holds design_aiger.aig, design_btor.btor, design_btor_single.btor, design_smt2.smt2, one per family.
[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

Measured: the header of tog2 written with 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
Fieldtog2Meaning
M33largest variable index
I3inputs: clk, rst, en
L8latches: the state bits
O0outputs (the flow removes them)
A22AND gates: the size of f
B1bad-state properties: one assert
C3constraints: the assumptions

L and A are the first two numbers a formal engineer reads: how much state, how much logic.

LatchesCountWhy they exist
t0, t12the design flops
f_past_valid, t0_q, t1_q3the history the property needs ($past, written as flops)
two unnamed flops2they sample the clocked assertion at the edge
one more latch1$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

Measured BTOR2 lines for ab_cnt, two 4-bit counters. One 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_11

BTOR2 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

Modeltog2tog3 (one more flop)
$dff cells after prep56
SMT2 registers78
BTOR2 state lines89
AIGER headeraag 33 3 8 0 22 1 3 0 0aag 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

QuestionAnswer
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.

  1. Run it

    Read the verdict and the SBY summary. A bounded PASS at depth 5 covers samples 0 to 3.

  2. Add a third flop

    Rename the module and top to tog3, add t2 that toggles when t0 && t1 && en, and re-run. The verdict does not change: no assertion reads t2.

  3. Break the cone

    Delete if (t0) so t1 toggles on en alone, 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

Predict the outputtog2 with t1 toggling on en alone, mode bmc, depth 5, smtbmc yices, reset assumed in step 0. SBY reports the clocked failure at step:
module tog2(input clk, rst, en, output reg t0=0, output reg t1=0);
  always @(posedge clk)
    if (rst) begin t0 <= 0; t1 <= 0; end
    else if (en) begin
      t0 <= !t0;
      t1 <= !t1;          // bug: toggles without t0
    end
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  // 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
endmodule
Concepttog2 has two design flops, yet its AIGER header has L = 8. Where do the extra latches come from?
ConceptWhich statement about the transition system is wrong?
ConceptWhy does the model need $initstate?
ConceptIn the BTOR2 file of ab_cnt, which line is part of the next-state function of a?

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.