What changed, why, and what to do about it.
Most projects need no changes. Backward compatibility in
v2 is interface-level and non-negotiable: every v1 design, cocotb
testbench, ryusim CLI invocation, and
Makefile.ryusim flow runs and passes on v2, and code-coverage
results are equivalent.
What is not promised is bit-for-bit waveform identity and event-ordering identity. v2 replaces the v1 settle loop with a full IEEE 1800-2023 stratified region scheduler, so the order in which independent, unordered events resolve can differ. The VPI callback contract that cocotb depends on is preserved exactly.
If a testbench passes on v1 and fails on v2 in a way that traces back to event order, the testbench is depending on an ordering the language does not guarantee. v2 does not offer a "v1 ordering" mode — the fix is to make the dependency explicit:
RisingEdge/ReadWrite/ReadOnly in
cocotb; a clocking block or program block in native
SystemVerilog) rather than on a bare settle.| Change | Opt-out / control |
|---|---|
fork blocks now execute (v1 warned and dropped them) |
none — this is the LRM behavior |
| Assertions now evaluate (v1 ignored immediate and concurrent assertions) | --assertions=off |
| Uninitialized 4-state variables read X | --no-x-init, or RYUSIM_ZERO_INIT set to anything but 0/false |
| Unimplemented constructs are compile errors (v1 warned and dropped) | --unsupported=warn |
Design-side $display/$monitor output now prints |
none — remove the calls if you do not want the output |
All top-level modules elaborate; --top selects the primary root without suppressing siblings |
pass only the sources you want elaborated |
| VCD/FST headers carry the design's timescale (v1 hardcoded 1 ns) | none — regenerate golden waveforms |
| Coverage output is now written on the cocotb/VPI path (v1 wrote none) | none — omit the coverage flags to suppress |
| Coverage files gained covergroup and four-outcome assertion records | records appear only when the matching coverage flag is passed |
Generated simulation binaries are built with a host-probed -march |
RYUSIM_MARCH=x86-64 (or off) |
--gpu is a no-op that warns |
drop the flag |
fork blocks now executeIn v1 any design containing a fork was diagnosed as
non-synthesizable and the fork body was dropped — the forked branches
never ran. v2 implements IEEE 1800-2023 §9.3.2 fork/
join/join_any/join_none, including
nested forks, deferred child start, per-spawn automatic-variable capture, and
disable fork/wait fork.
What to do: review any fork block that was
previously inert — its branches now take effect. There is no flag to
restore the v1 warn-and-drop behavior.
v1 parsed both immediate and concurrent assertions and then ignored them.
v2 implements clause 16: sequences, the property algebra, multiclock,
disable iff, sampled-value functions, sequence local variables,
recursive properties, expect, let, and the
assertion control tasks. An assert property that legitimately
fails on your stimulus now reports (an $error-class message
naming the assertion and its attempt start tick; simulation continues past
the failure unless the assertion's own action block says otherwise).
Related: an assert property whose sampling clock had no other
consumer in the design used to be silently eliminated along with its clock
generator, so it never evaluated at all. It now evaluates — assertions
on a free-running clock that nothing else observes will fire where they
legitimately fail.
What to do: fix the assertion or the stimulus. If you need
a staged migration, compile with --assertions=off (equivalent to
$assertoff at time 0: concurrent-assertion evaluation is
suppressed and coverage records the attempts as DISABLED); a
runtime $asserton re-enables, and
$asserton/$assertoff/$assertkill/
$assertcontrol give per-scope control.
Per LRM 16.3, checking an immediate assume as if it were an
assert is an optional capability, so RyuSim does not do it by default; pass
--assume-check to opt in. Concurrent
assume property always checks.
v1 zero-initialized variables at construction. v2 applies the IEEE
1800-2023 rule by default: an uninitialized 4-state variable
(reg/logic/integer, with no
declaration initializer and no time-zero write) reads X.
What to do: reset your design before sampling, which is
what the standard expects. X-initialization also surfaces designs that never
converge from an unknown state — if a simulation that used to terminate
now runs away, the missing reset is the bug. To stage the migration, compile
with --no-x-init, or set RYUSIM_ZERO_INIT to any value other than 0/false, to
restore the v1 zero-init emission.
v1 had two decoupled paths for constructs it did not implement: a diagnostic that warned, and a converter that silently dropped whatever it did not handle. v2 unifies both into one gate that fails the compile by default, so an unimplemented construct can never mis-simulate silently.
What to do: nothing, for almost every project — v2
implements the constructs v1 rejected. If you hit the gate during triage,
--unsupported=warn restores diagnose-and-continue.
In v1, $display, $write, $monitor,
and $strobe were dropped when the design was lowered, so
design-side output was observable only on the VPI/cocotb path. v2 executes
them in the design, so a standalone simulation prints them and the text
appears alongside your testbench's log output.
What to do: if a script parses simulator output, expect additional lines. Nothing else changes.
Per LRM 23.10.3 every module that is not instantiated by another module is
an elaboration root. v1 simulated only the module named by --top;
v2 elaborates every root, and --top now selects the
primary root (the one that drives VPI naming, the cocotb toplevel,
and the binary name) without suppressing the others. Single-root designs
— which is nearly every cocotb, validation, and production flow —
are unaffected.
What to do: if you relied on --top to prune
sibling top-level modules out of the simulation, pass only the sources that
contain the roots you want elaborated.
v1 wrote a hardcoded 1 ns timescale into every VCD and FST header
regardless of the design's `timescale directive. v2 writes the
design's own time unit and precision, so headers change for any design that
carries a timescale directive other than 1 ns, and timestamps in the file are
expressed in that unit.
What to do: regenerate any golden waveform captured on v1, and update tooling that assumes nanosecond ticks. See the Waveform Guide.
On v1, a cocotb run produced no coverage.info at all
— coverage was flushed only from the standalone simulation's main(),
which the VPI event loop exits before reaching. v2 writes the file on both paths, and
writes a zeroed template at time 0 so the file exists mid-run. If your cocotb flow
tolerated a missing coverage file, or read a stale one left over from a standalone run,
it now gets fresh output every run.
Where both versions produced a file, results are equivalent: the same lines and branches are covered, and the same signals are toggle-covered. Hit counts may differ, because the v2 scheduler legitimately evaluates a different number of times.
The LCOV file gained records for capabilities v1 did not have:
CGDA records for covergroups
(CGDA:<cg_type>,<inst>,<point_or_cross>,<bin>,<hits>,<at_least>),
emitted with --coverage-functional. ryusim coverage merge
writes * in the <inst> field, since merged rows union
by IEEE 19.11.3 name identity across instances.ASSERTA assertion records now carry four outcomes
— PASS, FAIL, VACUOUS,
DISABLED — instead of two, and are emitted with
--coverage-assert. v1 two-outcome files still parse and
merge unchanged.These records were labelled "subject to change until GA" during the v2
alphas. That caveat is now resolved: the formats above are the GA formats.
Both are RyuSim extensions to LCOV, like the existing TGDA
toggle records; strict lcov 2.x needs
--ignore-errors format to read them, and
ryusim coverage merge/ryusim coverage html handle
them natively. See the Coverage Guide.
obj_dir layout is not a contractobj_dir holds generated C++ and build scaffolding. Its file
names, file count, and internal structure are implementation detail and
changed substantially in v2 — they will keep changing across releases.
Only the CLI, the simulation binary it produces, and the artifacts you ask
for by flag (waveforms, coverage files) are stable surfaces.
What to do: do not build tooling that reads or patches
files inside obj_dir.
v1 passed no -march when compiling the generated simulation.
v2 probes the build host's psABI level and compiles the generated code for it,
which means a simulation binary built on one machine is not guaranteed to run
on an older one.
What to do: if you copy obj_dir binaries
between machines, set RYUSIM_MARCH — accepted values are
off (emit no -march), native,
x86-64, x86-64-v2, x86-64-v3, and
x86-64-v4. x86-64 is the portable baseline.
Every documented v1 invocation form still works. The changes are:
--gpu is a no-op. GPU offload is
parked and not carried forward in v2. The flag still parses, drives
nothing, and leaves the exit status and the generated artifacts identical
to a run without it; it prints one line on stderr:
warning: --gpu is deprecated and has no effect in v2 (GPU offload is parked; see release notes)
--gpu is a global flag and must precede the
subcommand (ryusim --gpu compile design.sv --top m) —
its placement has not changed.--threads N is new and opt-in: it enables multi-core
evaluation of the design side. The default is single-threaded, matching
v1. The RYUSIM_THREADS environment variable supplies the
default when the flag is absent.--assertions=on|off, --assume-check,
--no-x-init, --unsupported=warn|error, and
--coverage-functional are new and described above.The full list is in the CLI Reference.
The installer defaults to the stable channel, so a plain install gets the current v2 release:
curl -fsSL https://ryusim.com/install.sh | bash
--version X.Y.Z installs an exact version — including
a v1 release, for as long as you need one.--channel alpha (or RYUSIM_CHANNEL=alpha)
opts in to prereleases. Prereleases are never installed unless you ask:
they live in a separate listing, and an explicit prerelease
--version implies the alpha channel. If the alpha channel is
empty the installer fails loudly rather than quietly falling back to
stable.v2 is the current release line. The v1 maintenance line
(release/1.x) is no longer developed —
v1.7.66 is its most recent release — and a formal sunset
was announced with the v2.0.0-alpha.1 prerelease. That sunset
has not been executed: the branch is not locked, and no end
date has been set.
install.sh --version 1.7.x keeps working.New fixes land on v2 only. If something that worked on v1 does not work on v2 and it is not one of the deltas above, that is a bug — please report it.