Hardware · Verification Tools

Free SystemVerilog Simulators — What Actually Runs

Icarus, Verilator, AMD's Vivado simulator, Questa–Altera Starter and the slang front end, compared construct by construct — from putting nineteen interview-prep RTL challenges through them and recording every error message.

Icarus Verilog 12 Verilator 5.020 Vivado xsim Questa Starter slang Portability
01

What this deck covers

  1. Why it matters: parsing is not simulating
  2. The five tools
  3. The support matrix
  4. Interactive: which simulator runs my testbench?
  5. Verilator 5.020 — what broke, and the workarounds
  6. Icarus Verilog 12 — what broke, and the workarounds
  7. AMD Vivado simulator (xsim) — the free full-featured option
  8. Questa–Altera FPGA Starter Edition — the licence catch
  9. slang — a compiler, not a simulator
  10. Code that every tool should reject
  11. Testbench timing bugs no compiler will find
  12. Where the nineteen challenges stand
  13. Choosing a simulator — and the disk it needs
  14. Set-up recipes
02

Parsing is not simulating

The starting point was a review of the SystemVerilog coding challenges in three interview-preparation repositories — Interview_FPGA, Interview_DSP and Interview_RTL_LLM_Accelerators. Nineteen .sv files, each a design plus a self-checking testbench.

Challenge files
19
Did not compile
6
Compiled, failed when run
11
Now simulate & pass
19
Run only in xsim
10

A full SystemVerilog front end (slang) found the six files that did not compile. It could not find what came next. Once each testbench actually ran — nine in Verilator, the other ten in Vivado xsim 2025.2 — most of them failed:

ChallengeWhat simulation revealed
GELU approximationPlaceholder coefficients — worst error 818 LSB; 1,102 of 4,095 codes over the limit
FIR filterNever shifted out the Q1.15 coefficient bits, so every output saturated at 32767 — the passband test “passed” because 32767 > 5000
AXI DMA engineSTORE hung: WLAST compared against a burst length that shrank during the burst
Async FIFOEmpty/full flags two cycles late — a back-to-back read underflowed
RMSNormEvery output zero: three separate arithmetic and timing bugs in the Newton–Raphson stage
exp() LUTIndex taken from the wrong bits — ~100% error
Gray counter, FFT butterfly, accumulator treeTestbenches that sampled on the wrong cycle or ran tests concurrently
MAC unit FP16/INT8FP32 adder kept the hidden bit in the mantissa, so 2 + 2 − 3 gave 3.5; a clear in one mode wiped the other mode's accumulator
Softmax pipelineexp LUT mapped x = 0 to entry 128; the reciprocal's first guess was ≈1 for any sum below 256; two separate 2× scaling errors
KV-cache controllerCommand word fixed at 30 bits, so “read K” returned V; every write landed one beat after its data
Double-buffer controllerTile 0 went to the wrong bank; a DMA overwrote a tile not yet computed; one tile hung
Systolic arrayDid not elaborate (wire dimensions swapped); PE enables ignored the pipeline delay; the reference model computed A·Bᵀ in 8 bits
Softmax UVM-lite testbenchScoreboard would have passed a DUT that dropped outputs — it never checked the count
The lesson

A clean compile says the code is legal SystemVerilog. Only a simulator says it does what the comments claim. Which simulator you can use depends on which language features the testbench leans on — hence this deck.

03

The five tools

ToolVersion usedCost / licenceWhat it is
Icarus Verilog12.0Free, GPLEvent-driven interpreter (iverilog → vvp). Strong Verilog-2005, partial SystemVerilog
Verilator5.020 (Ubuntu 24.04 package)Free, LGPL/ArtisticCompiles SV to C++. Version 5 added --timing, so delays, fork and events work in testbenches. Newer releases than 5.020 support more
AMD Vivado simulator (xsim)2025.2Free in Vivado ML Standard edition (AMD account)Full event-driven simulator: classes, constrained random, covergroups, SVA, DPI-C, UVM 1.2
Questa–Altera FPGA Starter—Free with Quartus Prime Lite; one-year node-locked licence, renewableSiemens Questa, capacity-limited; verification features need a paid licence
slangpyslang 11.0Free, MITComplete SV parser and elaborator with precise diagnostics. Does not simulate

Commercial simulators (Synopsys VCS, Cadence Xcelium, Siemens Questa) support essentially the whole language, and are the reference the other tools are measured against.

04

The support matrix

✓ works◐ partly✗ fails? not established

O observed in this exerciseD vendor documentationK widely known, not re-tested here

The Icarus, Verilator and xsim columns are what those versions actually did with the challenge files (xsim 2025.2 ran the ten that the other two could not); cells marked D come from AMD's UG900 (Appendix B) or, for Questa Starter, Altera's product page and support forum.

05

Which simulator runs my testbench?

Tick the features your testbench uses. Each simulator's verdict is the weakest cell across your selection.

Presets: plain RTL + self-checking TB · RTL + SVA assertions · class-based TB with coverage · clear

06

Verilator 5.020 — what broke

Built with verilator --binary --timing --assert. Every row is a real message from a real file.

ConstructMessageWorkaround
covergroupUnsupported: covergroupNone in 5.020 — use xsim, or strip coverage for a functional run
##1 inside a sequenceUnsupported: ## (in sequence expression)Rewrite with |=> and $past, which do work
disable blk from a sibling fork branchdisable isn't underneath a begin with nameReplace the watchdog pattern with a shared done flag, or disable fork
Fixed array passed to an open-array [] argumentC++ compile: no match for operator= (VlQueue vs VlUnpacked)Declare the argument with a fixed size
virtual softmax_if handle for an instance with a parameter override, softmax_if #(.N(16))Unsupported: virtual interface never assigned any actual interfaceInstantiate the interface with its default parameters, or parameterise the virtual-interface type to match
Waiting on a clocking block through a virtual interface: @(vif.driver_cb)Internal Error: MemberSel of non-variableWait on the clock edge itself: @(negedge vif.clk)
typedef logic [7:0] t [N]; in a packageInternal: Unexpected CallWrite the range as [0:N-1]
Parallel build (-j 8)Internal Error: attempted to destroy locked Thread PoolIntermittent; -j 1 is reliable

What did work: concurrent assertions with |=> and $past, --timing delays and events, fork/join, automatic variables in always_ff, DPI-C with a linked C file, and real maths including $exp.

07

Icarus Verilog 12 — what broke

Compiled with iverilog -g2012. Icarus is the simulator the standalone RTL projects in this GitHub (AI_MMUL_Unit, AXI4_Crossbar, Cache_Controller_MESI, the RISC-V SoC) were verified with — usually under cocotb.

ConstructMessageWorkaround
Unpacked arrays as task/function portssorry: Subroutine ports with unpacked dimensions are not yet supportedPass a packed vector, or use module-level arrays
breaksorry: break statements not supportedLoop flag in the loop condition
Concurrent assertions (assert property)sorry: concurrent_assertion_item not supportedCompile with -gno-assertions, or use immediate assertions in always blocks
automatic variable inside a static blocksorry: Overriding the default variable lifetime is not yet supportedDeclare at module scope or in an automatic task
Function with an output argumentFunction … port c is not an input portMake it a task

Workarounds already in the RTL projects

08

AMD Vivado simulator (xsim)

Ships in every Vivado edition, including the free Vivado ML Standard. It needs no FPGA project: xvlog, xelab and xsim run from the command line.

Supported (UG900, Appendix B)

  • Classes, inheritance, parameterised classes
  • Mailboxes, virtual interfaces, clocking blocks
  • Arrays as subroutine arguments
  • disable fork; loop jump statements
  • Concurrent assertions: sequences, implication, iff
  • Constrained random: rand/randc, constraints, randomize()
  • Covergroups, including cross bins
  • DPI-C, including open arrays; UVM 1.2

Not supported

  • cover statements; assertion coverage
  • Deferred assertions
  • always/eventually/until/strong/weak properties
  • Multiclock assertions; global clocking
  • bind (binding code to scopes)
  • randsequence; tagged unions; pattern matching
  • One covergroup bin form (LRM §19.5.1.2)

Observed with 2025.2 — all ten remaining challenges now run and pass

Constructxsim 2025.2Workaround
$past() in an assertion's else actionXSIM 43-4463: unable to infer clocking eventPrint $sampled(sig)
Size cast 16'(a - b) in an assignNot truncated — silently gives −39936 (correct in procedural code; Icarus truncates)Declare the net at the width you need
shortreal arithmeticHeld at double precision; only $shortrealtobits rounds (Icarus behaves the same)Round each step through $shortrealtobits
$urandom(seed); as a statementurandom system task is not supportedprocess::self().srandom(seed)
illegal_bins … iff (…)Warning 43-4398: the iff is ignored — the value leaves every other bin and is illegal in any modeCheck the rule with an assertion
option.cross_auto_bin_max = 0Rejected: “String value is expected”An ignore_bins for the complement

Everything else the ten needed ran as documented: classes, mailboxes, parameterised virtual interfaces with clocking blocks, named disable across fork branches, ## sequences, break, array arguments, DPI-C, and covergroups with transition, iff and with bins. xsim is also stricter than slang: it rejected a variable driven by an always_ff and another process (three of the original files), an out-of-range constant index, and a module-scope variable used before its declaration — slang passed all three.

09

Questa–Altera FPGA Starter Edition

Free with Quartus Prime Lite. The licence comes from Altera's Self-Service Licensing Center, is node-locked, and must be renewed every year. The Starter edition is capacity-limited; the paid Altera FPGA Edition raises the limit.

The catch

randomize, randcase, randsequence and covergroup need a separate verification licence. That rules out exactly the class-based, coverage-driven testbenches that Icarus and Verilator also struggle with.

It remains a good simulator for plain RTL with self-checking testbenches, and for mixed VHDL/Verilog designs — but for those, Icarus and Verilator already do the job without a licence to renew.

10

slang — a compiler, not a simulator

slang parses and elaborates the full IEEE 1800-2017 language and reports errors precisely. Through pip install pyslang it is the quickest way to answer “is this legal SystemVerilog?”.

What it caught

  • All six non-compiling files, with line numbers
  • An assertion naming a signal that did not exist in the testbench
  • Packed structs holding unpacked arrays
  • binsof used outside a cross
  • Unbounded [*8:$] in a transition bin

What it missed

  • $abs — not an IEEE system function — elaborated without a warning; Verilator rejected it
  • A variable written by an always_ff and by another process (illegal); a module-scope variable used before its declaration; a constant index one past the end of a wire array — xsim rejected all three
  • Every functional bug on slide 02 — none of them is a language error

Use it as a pre-commit lint across every file. Then simulate.

11

Code that every tool should reject

These were in the challenge files. They are not simulator limitations — the language forbids them, or leaves the result undefined.

MistakeFix
Declaration after a statement in a begin…endHoist every declaration to the top of the block
$abs(x)Not a system function: write (x < 0) ? -x : x in a helper
struct packed containing unpacked arraysUse an unpacked struct
$error("a" "b") — adjacent literalsSV does not concatenate them: use one literal
$display("=" * 60)That is Python. Use {60{"="}}
A variable declared inside a covergroupMake it a class member
binsof() in a coverpoint's binsOnly legal in cross bins — use bins b = {v} iff (cond);
STALLED[*8:$] in a transition binRepeat ranges must be bounded: [*8:16]
drv = new(vif, fifo) where fifo = new() is a sibling declarationStatic initialiser order is undefined — build components in an initial block
NR_W'(a * b) expecting a wide productA cast makes its operand self-determined — a * b is only NR_W bits and overflows. Multiply into a wide temporary first
A variable assigned in an always_ff and also in an initial or a second always_ffIllegal: an always_ff variable may have no other driver. Merge the logic into one block, or use plain always in testbench code
DPI open array shortint x[] with a C short * parameterOpen arrays arrive as svOpenArrayHandle. Use a fixed-size SV array to get a plain pointer
12

Testbench timing bugs no compiler will find

Many of the nineteen challenges needed testbench fixes as well as — or instead of — design fixes. Four patterns kept recurring.

Driving on the sampling edge

Blocking writes straight after @(posedge clk) race the DUT's flops. Drive on @(negedge clk); sample at @(posedge clk); #1, after the non-blocking updates.

Hard-coded latency

An accumulator tree has log₂N adder levels plus an input register. Waiting log₂N edges reported every result one test late. Wait for valid_out, then check the latency.

Missing a one-cycle pulse

Waiting a fixed number of edges and then testing a valid that is high for one cycle only either catches it or silently skips the check. Loop until it appears, with a timeout.

Tests that run at once

Separate initial blocks all start at time 0. A reset test fired in the middle of a counting test. Sequence tests with flags or a single initial.

Make “PASS” mean something

The DMA testbench printed PASSED whatever the data, the FIFO scoreboard popped its expected value without comparing it, and several of the xsim ten printed PASS lines no failure could reach. Count failures, report the count, and try deliberate corruptions (mutants) to see each one caught.

13

Where the nineteen challenges stand

All nineteen now simulate and pass: nine in Verilator 5.020, the other ten in Vivado xsim 2025.2, which is the only free simulator here that runs them.

RepositoryChallengeStatusWhy not Verilator / IcarusFixed when first run
Interview_DSPFIR filterpasses (Verilator, Icarus), both forms—Design: Q1.15 coefficient shift
Interview_DSPFFT butterflypasses (Verilator)—TB sampling
Interview_FPGAGray-code counterpasses (Verilator, xsim)—TB sampling; $past portability
Interview_FPGAAsync FIFOpasses (Verilator)—Design: flag timing. TB: scoreboard
Interview_FPGACDC handshakepasses (xsim)Verilator: ##; Icarus: lifetime overrideTB: promised reset test added; assertions now fail the run
RTL_LLM_AcceleratorsAccumulator treepasses (Verilator)—TB latency
RTL_LLM_AcceleratorsAXI DMA enginepasses (Verilator)—Design: WLAST. TB: verdict
RTL_LLM_AcceleratorsGELU approximationpasses (Verilator)—Design: coefficients
RTL_LLM_AcceleratorsRMSNormpasses (Verilator)—Design: Newton–Raphson stage
RTL_LLM_Acceleratorsexp() LUTpasses (Verilator)—Design: index bits
RTL_LLM_AcceleratorsSystolic arraypasses (xsim)Verilator: named disable; Icarus: array portsDesign: wiring, PE enable timing. TB: reference model, verdict
RTL_LLM_AcceleratorsMAC unit FP16/INT8passes (xsim)Verilator: open-array argument; Icarus: breakDesign: FP32 add + RNE, clear, FTZ, saturation. TB: bit-exact checks
RTL_LLM_AcceleratorsSoftmax pipelinepasses (xsim)Verilator: named disable; Icarus: lifetime overrideDesign: exp index, reciprocal, scaling, state reuse. TB: per-weight checks
RTL_LLM_AcceleratorsKV-cache controllerpasses (xsim)Verilator: named disableDesign: command packing, write path, FIFO pop, seq_len. TB: reference memory
RTL_LLM_AcceleratorsRoPE unitpasses (xsim)Verilator: named disable; Icarus: array portsDesign: done timing. TB: bit-exact model, real angles
RTL_LLM_AcceleratorsTile schedulerpasses (xsim)Verilator: named disableDesign: double always_ff driver, done pulse. TB: monitor
RTL_LLM_AcceleratorsDouble-buffer controllerpasses (xsim)Verilator: named disable; Icarus: SVADesign: rewritten bank tracking. TB: bank-content models
RTL_LLM_AcceleratorsSoftmax UVM-lite testbenchpasses (xsim, DPI-C)Verilator: covergroup, virtual interfaceTB: output-count check. Portability: RNG seeding
RTL_LLM_AcceleratorsCoverage planpasses (xsim)Verilator: covergroupModel: bin arrays, K bins, NaN rule, cross. TB: expected coverage
14

Choosing a simulator — and the disk it needs

JobUse
Is it legal SystemVerilog?slang — seconds per file, exact diagnostics
Synthesisable RTL, directed self-checking testbenchVerilator (fast, compiled) or Icarus (simplest); both with cocotb
RTL plus |=>/$past assertionsVerilator — avoid ## in 5.020
Classes, mailboxes, constrained random, covergroups, UVMxsim from free Vivado — the only free option here that covers them
Anything, as the final wordA commercial simulator (VCS, Xcelium, Questa) where available

Disk and drive

15

Set-up recipes

slang — elaborate a file (pyslang 11)
pip install pyslang
python -c "from pyslang.syntax import SyntaxTree; from pyslang.ast import Compilation
c = Compilation(); c.addSyntaxTree(SyntaxTree.fromFile('tb.sv'))
print([str(d.code) for d in c.getAllDiagnostics() if d.isError()])"
Verilator without root — unpack the Debian package
apt-get download verilator && dpkg -x verilator_*.deb vl
export VERILATOR_ROOT=$PWD/vl/usr/share/verilator
ln -s $PWD/vl/usr/bin/verilator_bin $VERILATOR_ROOT/verilator_bin   # wrapper looks here
vl/usr/bin/verilator --binary --timing --assert -j 1 --top-module tb tb.sv [model.c]
./obj_dir/Vtb
Icarus
sudo apt install iverilog
iverilog -g2012 -s tb -o tb.vvp tb.sv && vvp tb.vvp
Vivado simulator, command line, with a DPI-C model
source /opt/Xilinx/2025.2/Vivado/settings64.sh   # xvlog, xelab, xsim, xsc
xsc softmax_golden.c                     # builds xsim.dir/work/xsc/dpi.so
xvlog -sv challenge_02_softmax_testbench.sv
xelab softmax_testbench -sv_lib dpi -s snap
xsim snap -R