vlsiNote EN 中文
Author verified by published 2026-10-22 07:51 UTC updated 2026-10-22 07:51 UTC 4 views

Verilator syntax support boundaries, measured: what Verilog/SystemVerilog runs and what fails silently

Other languages 中文 (original)English
Verilator 5.052 covers far more than “synthesizable RTL”: delays, real arithmetic, open-drain pullup/nmos, tri-state and SVA work.

The short answer

Verilator 5.052 covers far more than “synthesizable RTL”: delays, real arithmetic, open-drain pullup/nmos, tri-state and SVA work. It lacks gate-level precision; its worst failures are silent.

How this was established: the same source was given to Verilator 5.052 and VCS 2023.12 for A/B comparison; the support matrix comes from 39 standalone cases (one file per construct), each linted, built and run — not copied out of documentation.

Environment and method

item value
simulator Verilator 5.052 (2026-09-05), gcc 12.5.0
platform CentOS 7.9 / glibc 2.17, no root
reference VCS 2023.12-SP1
method one file per construct → verilator --lint-only --timing -Wno-fatal → the suspicious ones again with --binary --timing build + run
tally 39 cases: lint 38 PASS / 1 FAIL; the one that FAILs (tran) also fails to build

What a real design needed changed (brief)

Subject: a 1080p timing controller on a 7-series FPGA, 29 modules, 100 MHz input, 490 MHz LVDS serial output, I2C slave; the testbench runs 43 ms of simulation time.

Four changes are mandatory:

# change why
1 $fsdbDumpfile/$fsdbDumpvars/$fsdbSuppress → $dumpfile/$dumpvars no Verdi PLI; $fsdb* is an unknown system task
2 unisims + secureip → behavioural fast models secureip is encrypted source Verilator cannot read, and the unisim SERDES depend on it
3 instantiate glbl glbl(); inside the testbench some primitives use the XMR glbl.GSR, and the instance must be named exactly glbl
4 waveform format → VCD or FST see “Waveform pitfalls”

Result: the two BMP frames the TB dumps are byte-identical to the VCS run (same md5), and the end time agrees to 1 ps; build 4.6 s (-j32) against 23 s for VCS, simulation 52 s against 51 s.

Syntax support matrix (measured)

Fully supported, and identical to VCS

category constructs
sequential / combinational blocks always_ff / always_comb / always_latch / always @(*)
composite types packed struct, unpacked struct, packed union, typedef enum + '( ) casts
packages and interfaces package + import, interface + modport
classes class, new, virtual function (an implicit static lifetime draws IMPLICITSTATIC)
containers dynamic array int a[], queue int q[$], associative array int tbl[string]
strings string, $sformatf
assignment patterns '{default:1'b1} packed pattern, '{0,1,2,3} array pattern
generate generate for + genvar
assertions immediate assert(...) else $error, concurrent assert property (really executed, see below)
coverage covergroup/coverpoint + --coverage (verilator_coverage reports covergroup bin hit rates)
process control fork/join, join_any, join_none, wait fork, disable fork, disable {named fork}
procedural continuous assignment force / release
files and randomness $readmemh/$readmemb, $fopen/$fwrite/$fclose, $urandom, randcase
delays procedural #(0.250ns), continuous assignment assign #(real variable)
tri-state and gate level pullup/pulldown, nmos/pmos, and/nand/or/nor/xor/buf/not, tri inout
other casez/casex, unique case, signed arithmetic, array ports, let, inside, XMR (u_sub.sig read/write), DPI-C import

Two conclusions that go against older folklore, both settled by measurement:

  • disable {named fork} is supported. fork: W ... join_any; disable W; leaves only the winning branch printing; disable fork works as well.
  • SVA is not merely parsed. On a violation it really prints %Error: Assertion failed ... %Error: Verilog $stop and aborts the simulation — treat that as a failure in CI.

Not supported (hard error)

construct error
tran / tranif0 / tranif1 bidirectional switches %Error-UNSUPPORTED: Verilog 1995 gate primitive: tranif1, the build fails outright
encrypted vendor models (secureip and friends) the source cannot be read, so it cannot be parsed
$fsdb* and other Verdi-only system tasks no matching PLI, unknown system task
gate-level / SDF back-annotation, $setup/$hold timing checks no equivalent implementation

Accepted but silently wrong (the class to watch)

construct Verilator VCS note
drive strength: pullup against assign (weak0,weak1) s=0 0 1 no warning; any two drivers are resolved by value, strength plays no part
specify ... endspecify path delay y toggles at 2000 ps y toggles at 3000 ps only a %Warning-SPECIFYIGN, the delay is dropped
$dumpoff / $dumpon parsed but has no effect works the dump keeps writing to the end of the run
--no-timing or +notimingchecks every #delay becomes %Warning-STMTDLY: Ignoring delay, all edges collapse to t=0 — with -Wno-fatal this silently changes behaviour

On --timing: 5.x has it on by default, so not passing it gives the same result; what you must guard against is turning it off (or +notimingchecks, which is “accepted and ignored”). Write --timing explicitly in the script anyway, so the intent stays on the command line.

Waveform pitfalls

  1. $dumpvars needs --trace (VCD) or --trace-fst (FST) at build time. Without that compile-time switch $dump* is a silent no-op: the run finishes with no file and no message.
  2. $dumpoff/$dumpon do nothing (see the table above), so “dump only one window” is impossible; the only knob is the level argument of $dumpvars(level, scope).
  3. Three orders of magnitude in size: for the same 43 ms design with two 490 MHz buses, dumping only the TB and the DUT top level — VCD 12.4 GB, FST 123 MB. There is no equivalent of Verdi’s suppress, so the only levers are scope level or FST.
  4. Dumping is itself the main cost: same configuration, 52 s with no waveform, 152 s with FST (about 3x), about 98 s with VCD; on the VCS side enabling FSDB adds 0 s. If you want speed, do not dump by default.
  5. Verdi cannot read FST, GTKWave reads it natively; building FST support also needs the lz4 headers (fst2vcd converts to VCD for other tools).
  6. $time returns an integer number of time units: in a 1 ns/1 ps design a %0t print shows 42922750.000 ns, which looks 355 ps worse than VCS, while the last timestamp in the VCD is 42922749645 ps — the two agree. Judge the time axis from the waveform timestamps, not from the $time print.

Rules when writing TB / RTL (checklist)

  1. Do not start a comment with // Verilator. Verilator treats // verilator ... as a pragma and fails with %Error-BADVLTPRAGMA: Unknown verilator comment. Use a prefix such as // NOTE: or // VLNOTE:.
  2. Check whether a behavioural model of the primitive exists. An IP available only as encrypted secureip is a dead end; where a fast behavioural model exists, functional simulation is fully adequate.
  3. An XMR into a global (such as glbl.GSR) needs the right instance name: write glbl glbl(); in the testbench (instance named like the module); Verilator resolves it by searching upward.
  4. -Wno-fatal must come with warning triage. Grep the UNSUPPORTED / SPECIFYIGN / STMTDLY / WIDTHEXPAND / MULTIDRIVEN codes out and judge each one; using -Wno-fatal as “zero warnings” lets the silent distortions through.
  5. A failing assertion $stops the simulation, so a regression script must treat it as a failure in both exit code and log matching, not as “the run finished”.
  6. Do not rely on drive strength or gate delays. The common open-drain plus pull-up topology (“strong0 overrides pull1”) comes out right, but “pull1 overrides weak0” is wrong, and there is no warning.
  7. Run --lint-only --timing before building: clearing the syntax problems in the lint stage is much faster than trying again with a 4 s compile each time.
  8. Give the dump only the levels you need: $dumpvars(1, tb) plus $dumpvars(1, dut) is usually enough to locate a problem; dumping the whole hierarchy costs an unreasonable amount of size and time.

When not to use Verilator

scenario reason
gate-level / post-layout simulation, SDF back-annotation no specify/gate delays, no timing checks
models that depend on drive strength strength does not take part in resolution, silently wrong
IP available only as an encrypted model the source cannot be read
a flow tied to Verdi ($fsdb*, KDB) no PLI; waveforms only as FST/VCD
expecting a large simulation speed-up here 52 s against 51 s: 5x faster to build, no faster to run (clock edges dominate, not event scheduling)

How to reproduce

One case per construct, one file each, lint first and build second:

# a single construct
verilator --lint-only --timing -Wno-fatal --top-module m case.sv
# the suspicious ones (assertions / coverage / process control) really run
verilator --binary --timing -Wno-fatal --coverage --top-module m -o V case.sv && ./obj_dir/V
# coverage summary (reports covergroup hit rates)
verilator_coverage coverage.dat

To check “does it agree with the commercial simulator”, give the same file to both simulators and compare the printed times and values (every “identical / different” in this note comes from doing exactly that).

Limitations

The data here comes from a single Verilator version (5.052), 39 syntax cases and one real design of 29 modules, so it does not describe how earlier or later versions behave; program, clocking blocks and DPI-C were only proven to parse/build, their runtime semantics were not checked case by case. Re-run the lint matrix after upgrading Verilator.

VerilatorVerilogSystemVeriloglint仿真translation

Credibility

How far would you trust this note? 0-10.

How this note earned its label

Author verified bill2026 2026-10-22 07:51 UTC

Comments (0)

No comments yet.
Markdown source RSS Share card: light · dark The author checked this note line by line.