How RyuSim initializes variables (X or zero), what 2-state mode changes, and why results differ.
A SystemVerilog variable with no initializer and no reset has to start
somewhere. The standard says where, and RyuSim follows it by default.
Other simulators start in other places, so the same design can print
different results. This page covers the default, the options that change
it, the --2state mode, and what to expect when the results
disagree.
IEEE 1800-2023 clause 6.8, Table 6-7, gives the start value of a variable declared without an initializer. RyuSim uses these values unless you pass one of the options below.
| Declared type | Start value | Examples |
|---|---|---|
| 4-state integral | all X | logic, reg, integer, time |
| 2-state integral | 0 | bit, byte, int, longint |
real, shortreal | 0.0 | |
string | "" | |
| class handle | null |
A net is not a variable. An undriven wire reads Z.
X is not a value the hardware holds. It records that the simulator does not know the value. An X spreads through arithmetic and most operators, so a register that is never reset stays X for as long as it feeds on itself.
This counter has no reset. count is a 4-state
logic, count2 is a 2-state bit, and
mem is a 4-state memory that nothing writes.
module init_demo;
logic [3:0] count; // 4-state, no initializer, no reset
bit [3:0] count2; // 2-state, no initializer
logic [7:0] mem [0:3]; // 4-state memory, never written
logic clk = 0;
always #5 clk = ~clk;
always @(posedge clk) begin
count <= count + 1;
count2 <= count2 + 1;
end
initial begin
repeat (3) @(posedge clk);
#1;
$display("count=%0d count2=%0d mem[0]=%0d", count, count2, mem[0]);
if (count == 3) $display("count reached 3");
else $display("count did not reach 3");
$finish;
end
endmodule
Compile and run it with no options:
ryusim -q compile init_demo.sv --top init_demo -o x_init
./x_init/build/init_demo_sim
RyuSim - Simulation of init_demo
RyuSim: effective root seed = 676780200730893571 (source: randomly generated)
count=x count2=3 mem[0]=x
count did not reach 3
Simulation complete. Time: 26
count starts X, and X + 1 is X, so it is still X after
three clocks. count2 starts at 0 and counts to 3.
mem[0] was never written, so it reads X.
Now compile the same file with every uninitialized variable and memory starting at zero:
ryusim -q compile init_demo.sv --top init_demo --init-reg zero --init-mem zero -o zero_init
./zero_init/build/init_demo_sim
count=3 count2=3 mem[0]=0
count reached 3
The two runs above disagree on a value and on a branch.
count is X
because its start value is unknown. Under zero-init the simulator assumes
0, which real hardware does not promise: a flip-flop without reset powers
up to 0 or 1.count == 3 compares X with 3,
and the result is X. Clause 12.4 says an if whose condition is
X or Z is false, so the else branch runs. Under zero-init the
condition is true and the other branch runs. A case or
while can diverge the same way.Neither result is wrong for the options it was compiled with. The X
result reports a real fact about the RTL: nothing sets
count before it is used. Zero-init hides that. If a design
passes only under zero-init, look for a register or memory that is read
before reset or before its first write. The usual fix is a reset, or an
initializer if the target technology supports one.
To test whether a result depends on start state, compile with
--init-reg random --init-mem random and run with two seeds
(see Random start values). A result that changes with
the seed reads uninitialized state.
These options belong to ryusim compile. They apply at
compile time: they choose the start values written into the generated
model. To change the policy, recompile. Rerunning an existing build does
not change it.
| Option | Default | Applies to |
|---|---|---|
--init-reg <x|zero|one|random> | x | Uninitialized static 4-state variables: scalars, packed vectors, packed structs. |
--init-mem <x|zero|one|random> | x | Uninitialized static 4-state unpacked arrays and memories. |
--no-x-init | off | Same as --init-reg zero --init-mem zero. Kept for existing scripts. |
--2state (alias --two-state) | off | Two-state mode; see below. Sets both start values to zero unless --init-reg or --init-mem says one or random. |
The values:
x: all bits X. The IEEE default.zero: all bits 0.one: all bits 1.random: each bit 0 or 1, drawn from the run's seed. Never X or Z.The two buckets are independent. With only --init-reg zero,
the counter starts at 0 but the memory still starts X:
ryusim -q compile init_demo.sv --top init_demo --init-reg zero -o reg_zero
./reg_zero/build/init_demo_sim
For each bucket, the first rule that applies wins:
--init-reg or --init-mem.--no-x-init: zero.--2state: zero.x.Contradictory combinations stop the compile instead of picking a
winner. --no-x-init or --2state together with an
explicit x value is an error:
ryusim compile init_demo.sv --top init_demo --no-x-init --init-mem x 2>&1 || true
The build records the policy it used. ryusim-route.json in
the output directory has an init_reg and an
init_mem field:
grep -E '"init_(reg|mem)"' reg_zero/ryusim-route.json
"init_reg": "zero",
"init_mem": "x",
logic [3:0] v = 4'bxxxx;. They start at the initializer.bit, int, …). They start at 0 in every mode.wire still reads Z (except under --2state).real and string variables.function automatic. They still start X under --init-reg zero; only --2state starts them at 0.With random, the start bits depend only on the root seed
and the variable. The simulation prints the seed it used on its second
line. Pass +ryusim_seed=N or set RANDOM_SEED=N to
repeat a run exactly:
ryusim -q compile init_demo.sv --top init_demo --init-reg random --init-mem random -o rand_init
./rand_init/build/init_demo_sim +ryusim_seed=1
./rand_init/build/init_demo_sim +ryusim_seed=2
RyuSim - Simulation of init_demo
RyuSim: effective root seed = 1 (source: +ryusim_seed)
count=2 count2=3 mem[0]=226
count did not reach 3
Simulation complete. Time: 26
RyuSim - Simulation of init_demo
RyuSim: effective root seed = 2 (source: +ryusim_seed)
count=3 count2=3 mem[0]=174
count reached 3
Simulation complete. Time: 26
Seed 1 and seed 2 take different branches: the design reads its start state.
RyuSim has two C++ code generators. A few constructs, such as an
interface that contains an always procedure, still go to the
older one, which always starts storage at zero. RyuSim says so on stderr
when that overrides the requested policy:
interface pass_if(input logic clk_in);
logic clk;
always_comb clk = clk_in;
endinterface
module iface_demo;
logic clk = 1'b0;
always #5 clk = ~clk;
pass_if u_if(.clk_in(clk));
int n = 0;
always @(posedge clk) begin
n <= n + 1;
if (n == 3) $finish;
end
endmodule
ryusim -q compile iface_demo.sv --top iface_demo -o iface_x 2>&1
Warning: the legacy emitter zero-initializes all uninitialized storage — the requested init policies (reg=x, mem=x) are not honored for 1 legacy-routed module(s); IEEE X-init reads 0 there
Note: trigger schedule unavailable — a mixed route (1 legacy-routed module(s), e.g. 'pass_if'); this build runs the legacy-fixpoint adapter (route manifest "schedule": "legacy-fixpoint")
With --init-reg zero --init-mem zero there is nothing to
override, and the warning does not appear.
When both buckets are zero (from the options,
--no-x-init or --2state), a VPI write that
carries X or Z bits is collapsed to 0/1 before it reaches the design. This
matches Verilator, which has no X to store. A cocotb driver that parks a
bus at X then drives 0/1 values instead. Outside --2state, the
collapse prints no message. For cocotb setup, see cocotb.
Verilator is a two-state simulator. With its default options it starts
init_demo at zero. Verilator 5.038, built with
verilator --binary --timing init_demo.sv --top-module init_demo,
prints the same lines as the zero-init run above:
count=3 count2=3 mem[0]=0
count reached 3
RyuSim has two ways to match that start state:
| Options | Start state | Rest of the simulation |
|---|---|---|
--init-reg zero --init-mem zero | Zero, like Verilator | Still 4-state. X and Z from literals, undriven nets, division by zero and out-of-range reads behave as the IEEE standard says. |
--2state | Zero, like Verilator | Two-state, with Verilator's rules for X and Z. See below. |
Use the first when only the start state differs from Verilator. Use
--2state when the design or testbench also depends on
Verilator's handling of X and Z.
--2state)--2state builds the whole design as a two-state model.
Every integral signal holds 0 or 1. The rules follow Verilator's defaults,
which is a deliberate departure from IEEE 1800-2023. Without the flag
nothing below applies.
| Construct | Default (IEEE) | --2state |
|---|---|---|
| Uninitialized 4-state variable (6.8) | X | 0, or the explicit --init-reg/--init-mem value (one, random) |
| X or Z bits in a literal (5.7.1) | X or Z | 0. In casez/casex/case inside labels they are still wildcards. |
| Undriven net (6.6) | Z | 0 |
===, !== (11.4.5) | Compare X and Z bits | Compare as ==, != |
a / 0, a % 0 (11.4.3) | All X | 0 |
$isunknown (20.9) | 1 if any bit is X or Z | 0 |
| VPI or DPI write with X/Z bits | Stored as written | Collapsed to 0/1, with one message per signal |
This design exercises several rows:
module two_state_demo;
logic [3:0] a = 4'b10x1; // literal with an X bit
wire [3:0] w; // undriven net
logic [3:0] n = 4'd9, d = 4'd0;
initial begin
#1;
$display("a=%b w=%b", a, w);
$display("isunknown(a)=%0d", $isunknown(a));
$display("n/d=%b n%%d=%b", n / d, n % d);
end
endmodule
ryusim -q compile two_state_demo.sv --top two_state_demo -o ts_default 2>/dev/null
./ts_default/build/two_state_demo_sim
a=10x1 w=zzzz
isunknown(a)=1
n/d=xxxx n%d=xxxx
ryusim -q compile two_state_demo.sv --top two_state_demo --2state -o ts_2state 2>/dev/null
./ts_2state/build/two_state_demo_sim
a=1001 w=0000
isunknown(a)=0
n/d=0000 n%d=0000
init_demo under --2state prints the zero-init
result:
ryusim -q compile init_demo.sv --top init_demo --2state -o init_2state
./init_2state/build/init_demo_sim
--2state needs the newer code generator for every module.
If any module goes to the older one, the compile stops before it writes
the model. It does not fall back to a four-state build.--2state with --all-four-state, or with an
explicit --init-reg x or --init-mem x, is an
error.v[i+3] on a
4-bit v can still read X under --2state, and so
can an out-of-range read of an unpacked array used directly as a
$display argument. The standard's result for these reads is
X; the two-state rule is 0. Do not rely on either until this is
fixed.The iface_demo.sv design from above shows the refusal:
ryusim -q compile iface_demo.sv --top iface_demo --2state -o iface_2state 2>&1 || true
ryusim compile init_demo.sv --top init_demo --2state --all-four-state 2>&1 || true
A --2state build records "two_state": true in
ryusim-route.json. Every option on this page is listed in the
CLI reference.