X-Propagation

See how an unreset flop hides from two-state and four-state simulation, how formal turns it into a free value the solver picks, and what this engine does with an explicit x.

Lesson
F5.5
Time
15 min
Runs on
SymbiYosys
Check
4 questions

A mode register had no reset, because firmware was meant to write it before enabling the block. Every RTL test passed, and on silicon the flop powered up as 3 often enough to enable the block early. The gate-level run had shown X everywhere, and the team had spent two weeks blaming the netlist.

mode starts at 3, and no firmware write ever came
0123clkfreerstfreefw_wrfreestartfreemode3armedenfw_seenmode: free initial valuestarten, no firmware writeSBY reports FAIL

Measured with smtbmc yices at depth 20. The flop has no reset, so its first value is the solver's choice: 3. Sampled at step 2, reported at 3.

Three Ways to Read an Unknown

One Unreset Flop, Three ReadingsThe same path read by four-state simulation, a two-state simulator and formal. Arrows mean "feeds".
data: where the transaction goes
One unreset flop, three readingsThe same path read by four-state simulation, a two-state simulator and formal. Arrows mean "feeds". component mode[3:0], noreset four-state sim: X.Two-state sim: 0.Formal: any value,chosen per run component mode == 4'd3 four-state: X.Two-state: 0. Formal:1 if the solver chose3 component en = armed &&(mode == 3) four-state: X oncearmed component if (en) in aconsumer four-state: X countsas false, the elsebranch runs component gate-levelsimulation the X spreadsthrough the netlist:pessimism component silicon the flop powers up 0or 1 per bit;sometimes 3 optimism
Read the diagram as text
  • mode[3:0], no reset: four-state sim: X. Two-state sim: 0. Formal: any value, chosen per run
  • mode == 4'd3: four-state: X. Two-state: 0. Formal: 1 if the solver chose 3
  • en = armed && (mode == 3): four-state: X once armed
  • if (en) in a consumer: four-state: X counts as false, the else branch runs
  • gate-level simulation: the X spreads through the netlist: pessimism
  • silicon: the flop powers up 0 or 1 per bit; sometimes 3
  • mode[3:0], no reset → mode == 4'd3
  • mode == 4'd3 → en = armed && (mode == 3)
  • en = armed && (mode == 3) → if (en) in a consumer: optimism
  • en = armed && (mode == 3) → gate-level simulation
  • mode[3:0], no reset → silicon

Neither simulation says what the hardware does. Formal has no X: it treats the unknown as a value it may pick, and picks the one that breaks the rule.

In four-state simulation the flop starts as X. The compare gives X, and an if on an X takes the else branch: the RTL hides the unknown, which is X-optimism. Gate-level simulation spreads the X instead, which is pessimism. A two-state simulator starts the flop at 0 and the bug never appears.

Formal is two-valued. A flop with no reset and no initial value is a free choice in the first step (F1.6). The solver picks mode = 3, and the enable fires before any firmware write: FAIL at reported step 3.

A Sampler Against the Full Set

RunInitial value of modeEarly enables
Two-state simulation, defaultevery bit 00 in 10000 cycles
Random initial values, seed 1random, per seed0
Random initial values, seed 2random, per seed0
Random initial values, seed 3random, per seed0
Random initial values, seeds 1 to 100random, per seedfound by 11 of 100 seeds
Formal, depth 20all sixteen values at onceFAIL at reported step 3

Verilator 5.055 devel with --x-initial unique, run with +verilator+rand+reset+2 and a seed. A random start is a sampler: about one value in sixteen breaks the rule. Formal is the full set.

The two-state run printed 0 early enables in 10000 cycles, and so did seeds 1, 2 and 3. Over 100 seeds, 11 started mode at 3. A random initial value is one draw per run; formal took all sixteen in one run of 491 ms.

Two Fixes, and the x You Write

ChangeMeasured result
reset mode to 0 (FIX_RESET)BMC PASS to depth 20; UNKNOWN at k = 4 (base case passed, induction step failed); Prove PASS (abc pdr)
no reset, if ($initstate) assume(mode == 4'd0)BMC PASS to depth 20
assign dbg = 1'bx; with assert(!dbg)FAIL at reported step 2

Mode bmc at depth 20 unless named, smtbmc yices, reset assumed in step 0.

The reset fixes the design. The assumption fixes only the proof: it says firmware always writes first, which is now a promise someone must keep and a line for the sign-off review. The k-induction attempt needs a helper or PDR, because an unreachable state with mode at 3 and no write looks fine for four steps.

An explicit x becomes a free value too. With SBY's default script the solver's model gains $anyseq cells where the RTL wrote 1'bx (2 here, none without it), so the assert on dbg fails at once. Commercial X-propagation apps model more than this; this page claims only what the open engine does.

Asked in Interviews

QuestionAnswer
What is X-propagation checking?Finding unknowns (unreset flops, x assignments) that can reach an output or a control point, by treating them as values a solver may choose.
Why do RTL simulations miss it?X-optimism in if and case takes a branch, and a two-state simulator starts at one chosen value.
What is the fix?A reset, or a documented assumption that firmware writes first, reviewed at sign-off.

Try It

The rule in SVA

assert property (@(posedge clk) disable iff (rst) en |-> fw_seen);

fw_seen is a bench flop set by the first firmware write.

As this engine runs it

  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  reg fw_seen=0;   // firmware has written mode at least once (a write during reset counts too)
  always @(posedge clk) if (fw_wr) fw_seen <= 1;
  always @(posedge clk) if (!rst && en) assert(fw_seen);

A write during reset counts too, so the rule cannot blame the reset step.

The whole task. Depth is the default, 20.
[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top xcmp
[files]
design.sv
  1. Run the simulation twin

    Run the Verilator bench: 0 early enables. Which initial value did it use?

  2. Run formal

    Run as it is: FAIL at reported step 3. Read mode at step 0 and look for a firmware write.

  3. Add the reset

    Put rst in the mode branch and run: BMC PASS to depth 20.

  4. Assume instead

    Remove the reset and add if ($initstate) assume(mode == 4'd0);: BMC PASS to depth 20. Where should that line be reviewed?

  5. Write an x

    Add assign dbg = 1'bx; and assert(!dbg): FAIL at reported step 2.

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 outputxcmp with no reset on mode, mode bmc, default depth 20, smtbmc yices, reset assumed in step 0, if (!rst && en) assert(fw_seen). At which reported step does it fail?
module xcmp(input clk, rst, fw_wr, input [3:0] fw_data, input start, output en);
  reg [3:0] mode;            // no reset: firmware writes it before start
  reg armed=0;
  always @(posedge clk) begin
    if (1'b0) mode <= 4'd0;   // FIX_RESET puts rst here
    else if (fw_wr) mode <= fw_data;
    if (rst) armed <= 0; else if (start) armed <= 1;
  end
  assign en = armed && mode == 4'd3;
`ifdef FORMAL
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  reg fw_seen=0;   // firmware has written mode at least once (a write during reset counts too)
  always @(posedge clk) if (fw_wr) fw_seen <= 1;
  always @(posedge clk) if (!rst && en) assert(fw_seen);
`endif
endmodule
ConceptWhy does formal find what four-state simulation hides here?
ConceptWhich statement about this design is wrong?
Read the waveformIn the trace, which value is the solver's free choice, and which later value is its consequence?
mode starts at 3, and no firmware write ever came
0123clkfreerstfreefw_wrfreestartfreemode3armedenfw_seenmode: free initial valuestarten, no firmware writeSBY reports FAIL

Measured with smtbmc yices at depth 20. The flop has no reset, so its first value is the solver's choice: 3. Sampled at step 2, reported at 3.

Remember

  • Four-state simulation hides an unknown in if (optimism) or floods the netlist with it (pessimism); neither says what silicon does.
  • Formal is two-valued: an unreset flop is a free initial value and the solver picks the one that breaks the rule.
  • A random initial value is one draw per run; formal checks all values in one run.
  • Fix with a reset, or with an initial-state assumption that goes on the sign-off list. An explicit x is a free value too.