How to Simulate Verilog with Verilator
Goal: Load, compile, and simulate a Verilog/SystemVerilog design using the open-source Verilator simulator.
Note
This flow needs no Vivado or Xilinx license. It handles Verilog and SystemVerilog sources only; VHDL is a hard error (see Troubleshooting below). For a VHDL design, use How to Simulate VHDL with GHDL instead.
Prerequisites
Before running the simulation flow, ensure the following are in place:
Verilator 5.020 or newer, on PATH. The
--binary/--timingflags this flow relies on require timing support, which is not present in older releases. The version check runs inmake load_source_code, before any source is loaded.gtkwaveinstalled for waveform viewing.For Rogue co-simulation designs only:
gcc,pkg-config,libzmq4.1.0 or newer, and a surf version that providessimlink/verilator/.Project
Makefileincludessystem_verilator.mk.
Makefile Setup
Add the following to your project Makefile:
include $(TOP_DIR)/submodules/ruckus/system_verilator.mk
Set any knobs before the include line, the same way GHDLFLAGS is set
for the GHDL flow:
export VERILOG_INCDIRS = $(PROJ_DIR)/rtl/include
export VERILOG_DEFINES = SIM_SPEED_UP
export SIM_TOP = MyTb
include $(TOP_DIR)/submodules/ruckus/system_verilator.mk
Switching simulators means changing only this include line to
system_iverilog.mk (see How to Simulate Verilog with Icarus Verilog); every other knob is
shared between the two flows.
Loading Sources
A project’s ruckus.tcl tree is loaded the same way as every other ruckus
backend, through loadSource and loadRuckusTcl, using the same shared
loader as the Icarus Verilog flow:
source $::env(RUCKUS_PROC_TCL)
# A package that other files in this directory depend on
loadSource -path "$::DIR_PATH/rtl/MyPkg.sv"
# The rest of the RTL sources in this directory
loadSource -dir "$::DIR_PATH/rtl"
# Testbench-only sources
loadSource -sim_only -dir "$::DIR_PATH/tb"
The loader (shared/verilog_proc.tcl) applies the following rules:
.vand.svfiles are compiled in load order: the orderloadSource/loadRuckusTclcalls occur across the whole recursive load, not alphabetical or directory order. Verilator tolerates either package/user order (unlike Icarus, see How to Simulate Verilog with Icarus Verilog).loadSource -dirloads the directory’s.v/.sv/.vh/.svhfiles sorted by name, non-recursively.Duplicate files (loaded twice through different paths) are dropped by real path.
.vhand.svhfiles are never compiled; each loaded header’s directory is instead added as an include directory automatically.-libis accepted and ignored, since Verilog has no libraries.-sim_onlyis accepted and stripped; both flows compile everything in one filelist.Any
.vhd/.vhdlfile anywhere in the loaded tree is a hard error:load_source_codelists every offending path and exits before writing a filelist. If the VHDL came from surf,loadRuckusTcl $::env(MODULES)/surf/simlinkinstead of loading all of surf.No
BuildInfoPkg.vhd/BUILD_INFO_Cgeneric is generated: that mechanism is VHDL-only and would trip the rule above.
Steps
Load the source file list:
make load_source_codeChecks the Verilator version floor, loads the project’s
ruckus.tcltree, and writes the ordered, deduplicated filelist to$(OUT_DIR)/$(PROJECT).f.Build:
make buildRuns
verilator $(VERILATOR_FLAGS) [--trace-fst] +incdir+<dir>... +define+<def>... --top-module $(SIM_TOP) --Mdir $(OUT_DIR) -o V$(SIM_TOP) -f $(PROJECT).f [<OUT_DIR>/libRogueSimLinkDpi.so -LDFLAGS "-Wl,-rpath,$(OUT_DIR) <pkg-config libzmq libs>"]in$(OUT_DIR). When a Rogue SimLink leaf is in the filelist, this step also builds and links the SimLink DPI library first (see Rogue Co-Simulation below).Run:
make tbRuns
./V$(SIM_TOP) $(SIM_PLUSARGS)in$(OUT_DIR).make tbdepends onbuild, which depends onload_source_code, which depends ondir(which itself depends onclean), so a baremake tbalways runs the whole chain from a clean$(OUT_DIR), exactly like the GHDL flow.View a waveform:
make gtkwaveSets
WAVES=1, runsmake tb, and opens$(OUT_DIR)/$(PROJECT).fstin GTKWave.Print environment variables:
make test
Clean the build directory:
make clean
Waveforms
The testbench owns waveform dumping; Verilator does not dump anything on its own:
initial begin
$dumpfile("MyTb.fst");
$dumpvars;
end
WAVES=1 adds --trace-fst to the verilator build, so the dump above
is written in FST format. make gtkwave sets WAVES=1 for you and opens
the .fst file.
Note
Verilator’s FST writer needs the lz4 (and zlib) development
headers installed. Without them, make build WAVES=1 stops with
fatal error: lz4.h: No such file or directory (see Troubleshooting).
Rogue Co-Simulation
A design that instantiates surf’s flat SimLink wrappers, RogueTcpStreamWrap,
RogueTcpMemoryWrap, RogueSideBandWrap (loaded via loadRuckusTcl
$::env(MODULES)/surf/simlink; port and parameter contract documented as
plain text in surf’s simlink/sv/README.md, since it lives in another
repository), gets Rogue co-simulation automatically:
make builddetectsRogueTcpStream.sv,RogueTcpMemory.sv, orRogueSideBand.svin the filelist. If none is present, the rest of this section is skipped entirely and no libzmq check runs.When a leaf is detected,
make buildchecksgcc,pkg-config, andlibzmq>= 4.1.0, then runsmakein-tree in surf’ssimlink/verilator/, copies the resultinglibRogueSimLinkDpi.sointo$(OUT_DIR), and runsmake cleanin the surf tree.Unlike the Icarus flow, which loads its VPI module at
vvprun time, theverilatorbuild linkslibRogueSimLinkDpi.sodirectly, staged from$(OUT_DIR), with-LDFLAGS "-Wl,-rpath,$(OUT_DIR) <pkg-config libzmq libs>"so the resultingV$(SIM_TOP)binary finds the library and libzmq at run time with no additional setup.No environment setup (no
setup_env.sh, noLD_LIBRARY_PATH) is needed; the rpath resolves the library.A Rogue peer (a PyRogue client) connects to ports
NandN + 1of each wrapper’sPORT_NUM_Gonce the simulation is running.
Key Variables
Variable |
Default |
Description |
|---|---|---|
|
Top module name simulated by |
|
|
Flags passed to |
|
(empty) |
Whitespace-separated list of extra include directories, each added as
|
|
(empty) |
Whitespace-separated list of |
|
(empty) |
Plusargs appended verbatim after |
|
(empty) |
Set to |
|
|
Backend selector read by |
|
|
Git dirty-state check bypassed by default. |
Note
Parameter overrides go through the -G<name>=<value> escape hatch in
VERILATOR_FLAGS rather than a dedicated variable:
export VERILATOR_FLAGS = --binary --timing -j 0 -GWIDTH_G=16
Troubleshooting
- “VerilogCheckVersion: ruckus requires verilator 5.020 or newer”
The Verilator on PATH is older than the enforced floor (the
--binaryand--timingflags this flow relies on require 5.020 or newer). There is no bypass variable; install Verilator 5.020 or newer.
“libzmq package was not found”
libzmq package was not found Please make sure that you have libzmq installed or have sourced the necessary rogue setup scriptsOnly appears for a design containing a Rogue SimLink leaf. Install the
libzmqdevelopment package sopkg-configcan find it.
- “VerilogWriteFilelist: VHDL sources are not supported …”
A
.vhd/.vhdlfile is somewhere in the loaded tree. Remove it, or if it came from surf,loadRuckusTcl $::env(MODULES)/surf/simlinkinstead of loading all of surf.- Fatal lint warnings stop the build
Verilator’s default lint warnings are fatal. Add
-Wno-fataltoVERILATOR_FLAGSto continue past them (fix the warnings when practical instead of suppressing them permanently).- “%Warning-TIMESCALEMOD”
Mixing timescaled and non-timescaled modules in the same design raises this warning (fatal by default, see above). surf SimLink sources carry no timescale. Add
--timescale 1ns/1pstoVERILATOR_FLAGS.- “fatal error: lz4.h: No such file or directory”
Verilator’s FST waveform writer needs the
lz4(andzlib) development headers. Install them, or avoidWAVES=1/make gtkwaveif tracing is not needed.