A two-state simulator answers a different question than a four-state one.
Where that difference matters - reset-X, X in a case selector, X in a link-training register - the answer diverges.
This article measures the divergence, and what carrying X and Z as first-class values costs on a compute-bound loop.
Reset and initialization behaviour is where the difference between two-state and four-state modelling shows up sharpest: registers that come up unknown, muxes that select on an X, values that propagate through arithmetic before anything drives them. IEEE 1800 four-state semantics express those situations with X and Z as first-class values. Verilator (a compiled two-state simulator) does not preserve the fourth state - that is its documented design point, chosen for compile-time throughput. This article measures where FSimX and Icarus Verilog agree byte-for-byte on the semantics, where the two-state answer diverges from the four-state answer at a scale that stops a testbench, and what carrying X and Z costs on a compute-bound loop.
A note on how this series treats Icarus Verilog and Verilator. They are the open-source reference simulators FSimX corrects itself against. Where FSimX and Icarus agree byte-for-byte on four-state semantics, that agreement is how FSimX shows it is right. Where FSimX and Verilator disagree on X propagation, the disagreement is measured, not argued. The reference tells us the standard; where we scale past it - and by how much, and at what cost - is what the numbers report.
Five X-semantics checks, side by side
The probe is a WIOWIZ-original test, bench_xprop, that exercises five basic four-state behaviors from IEEE 1800: an uninitialized register, the logical identities on X, a conditional with an X select, a case statement with an X selector, and X in arithmetic. FSimX and Icarus Verilog (iverilog, the reference four-state event-driven simulator) agree byte-for-byte on all five. Verilator produces a different value on every one, with no warning and no error.
| Check (bench_xprop) | IEEE 1800 four-state (FSimX = iverilog) | Compiled two-state simulator |
|---|---|---|
| uninitialized reg | X | 00000000 |
| X&0, X|1, X^v | 0, 1, X | 00000000, 11111111, 10101010 |
| conditional with X select | 00xxxx11 (bitwise merge) | 00110011 |
| case with X selector | default arm (7f) | 01 (took the 00 arm) |
| X in arithmetic | xxxxxxxx | 00000001 |
Read the case-statement row. With an X in the selector, four-state semantics fall through to the default arm; Verilator's two-state model takes the zero arm and continues as if the selector were known. On this probe the divergence is one line in a table; on a reset controller with an uninitialised state machine it is the difference between hitting the default arm and taking a valid-looking path. Verilator's behaviour here is its documented design point, not a defect; the point of the probe is to make the difference visible where it matters.
When four-state changes the outcome, not just a value
Individual value differences are one thing. The more consequential case is when carrying X versus assuming zero changes whether a testbench finishes at all. The chiplet designs in this study show both directions of that.
The chiplet S3 (L2) adapter testbench, a WIOWIZ-original TileLink-to-UCIe adapter with an SVA checker, runs to a clean verdict on both four-state simulators, check for check:
$ # chiplet S3 (L2) adapter, four-state (FSimX and iverilog) SVA_SUMMARY: checks_pass=766 checks_fail=0 VERDICT: PASS FSimX total 0.07 s $ # chiplet S3 (L2) adapter, compiled two-state simulator SVA_SUMMARY: checks_pass=99937 checks_fail=0 VERDICT: PARTIAL_TIMEOUT loops to its 200 us watchdog, deterministic x3
FSimX and Icarus Verilog agree at 766 of 766 checks. Verilator builds the model and then never reaches the end condition: its differing initialization changes the stimulus progression, and the checker spins to its watchdog. The four-state pair agrees; Verilator does not finish.
The chiplet S3 (L4) two-die design - one configuration in a family of WIOWIZ chiplet build points - shows the two-state failure mode even more starkly, and it is also where FSimX itself still has an open divergence, so it is not presented here as a clean win. It runs RV32I firmware on a picorv32 core across a die-to-die link to a remote die. Under two-state, the link never trains:
$ # chiplet S3 (L4) two-die, compiled two-state simulator %Error: WATCHDOG: no trap after 30000 cycles (link_up cpu=0 mem=0 cpu_run=1 pc~0x00006ffc) -> Aborting...
FSimX reaches the firmware completion word 0x0000600d on this design; Verilator's two-state model, initialising the link-training register to zero rather than unknown, hangs with link_up cpu=0 mem=0 and aborts on every iteration. FSimX does not yet match Icarus Verilog end to end here, though: it diverges on 32 checks in the UCIe mainband-CRC layer (16 per die), where Icarus Verilog passes clean, so FSimX's overall verdict on this test is FAIL even though the firmware self-check still reaches 0x600d. That residual four-state divergence is under investigation (tracked as BUG_975); it does not affect the two-state result, which is that the link never trains at all. For the clean four-state agreement, read the chiplet S3 (L2) result above - 766/766 checks matching Icarus Verilog while the two-state tool times out.
Where FSimX loses on speed, with the exact number
On raw free-running RTL throughput, Verilator wins by a large multiplier on this compute-bound loop. The exact number:
On bench_lfsr, a WIOWIZ-original 500,000-cycle free-running linear-feedback shift register with identical checksums across all tools, the run-phase numbers are: Verilator's compiled two-state model 0.17 seconds, Icarus Verilog 20.01 seconds, FSimX 25.25 seconds. That makes Verilator about 148 times faster than FSimX on this compute-bound loop, and Icarus Verilog about 1.26 times faster. Those are the numbers, unrounded in the study.
Two framings on that gap. First, it is Verilator's design point - compile every process to optimised straight-line code, do not carry the fourth state, and on a long free-running loop with a build that amortises across many cycles, the throughput is far ahead. Second, the gap is specific to compute-bound free-running RTL; the front-end and build costs the throughput chart leaves out sit in the stage-anatomy figure below.
On the DeepFuse sensor-fusion testbench, FSimX reaches a running simulation in 0.69 seconds; Verilator needs 25.6 seconds to produce a runnable model, and only after dropping the covergroups it cannot compile. So on a short iterative edit-run loop, or a design where build wall dominates, FSimX wins wall-clock. On a long compute-bound run of already-built RTL, Verilator wins throughput. Blog 05 covers how FSimX narrows the throughput side without dropping four-state, and where that work has and has not paid off yet.
What the numbers say, in one line each
Four-state values are carried exactly by FSimX and match Icarus Verilog byte-for-byte across the five semantics probes. On the two chiplet workloads, Verilator's two-state initialisation causes divergence a testbench notices (766 vs 99937 checks, or hang on link training). On the free-running bench_lfsr loop, Verilator is about 148× faster than FSimX and Icarus Verilog is about 1.26× faster; that number is on the versions tested and on this loaded workstation. Which trade is right depends on which question the simulator is being asked.
Whether that trade is right depends on which question the simulator is being asked.
