Verilog Simulation: Verilator, Icarus Verilog, and GTKWave
Open-source Verilog simulation usually starts with three tools: Icarus Verilog for lightweight event-driven simulation, Verilator for fast linting and compiled simulation, and GTKWave for waveform inspection. Each tool answers a different question.
Learning Objectives
By the end of this lesson, you should be able to:
- choose Icarus Verilog or Verilator for a beginner simulation task;
- compile and run a small testbench from the command line;
- generate a VCD waveform and open it in GTKWave;
- use simulator warnings as design feedback;
- explain why simulation complements, but does not replace, synthesis and timing analysis.
Tool Roles
- Icarus Verilog: simple Verilog/SystemVerilog event simulation; friendly for classic HDL testbenches.
- Verilator: fast linting and compiled simulation; very strong warnings and best with synthesizable-style SystemVerilog.
- GTKWave: VCD/FST waveform viewing; useful for debugging but not a pass/fail method.
Use Icarus when you want the shortest path from top.v and top_tb.v to a runnable simulation. Use Verilator when you want stronger linting, speed, and CI-friendly checks.
Icarus Verilog Flow
Given:
rtl/counter4.v
tb/counter4_tb.v
Compile and run:
iverilog -g2012 -Wall -o build/counter4_tb.vvp rtl/counter4.v tb/counter4_tb.v
vvp build/counter4_tb.vvp
-g2012 enables SystemVerilog-era syntax support. -Wall enables useful warnings. The output file is a simulation program run by vvp.
VCD Waveform Dump
Add this block to the testbench:
initial begin
$dumpfile("build/counter4.vcd");
$dumpvars(0, counter4_tb);
end
Then run:
gtkwave build/counter4.vcd
Add only the important signals first: clock, reset, input controls, state, outputs, and any failing internal register. Too many signals can hide the timing relationship you need.
Verilator Flow
For a SystemVerilog testbench that Verilator can compile directly:
verilator --binary -j 0 -Wall --trace rtl/counter4.sv tb/counter4_tb.sv
./obj_dir/Vcounter4_tb
Typical meanings:
| Option | Meaning |
|---|---|
--binary |
Build a runnable simulator executable |
-j 0 |
Use available CPU threads |
-Wall |
Enable broad warnings |
--trace |
Enable waveform tracing when the testbench requests it |
Verilator is strict. Treat warnings about width, latch inference, unused signals, incomplete cases, and blocking/nonblocking style as review comments.
Minimal Self-Checking Testbench
`timescale 1ns/1ps
module counter4_tb;
reg clk = 0;
reg rst = 1;
reg en = 0;
wire [3:0] count;
counter4 dut (
.clk(clk),
.rst(rst),
.en(en),
.count(count)
);
always #5 clk = ~clk;
initial begin
$dumpfile("build/counter4.vcd");
$dumpvars(0, counter4_tb);
repeat (2) @(posedge clk);
rst <= 0;
en <= 1;
repeat (3) @(posedge clk);
#1;
if (count !== 4'd3) begin
$display("FAIL: count expected 3, got %0d", count);
$finish;
end
en <= 0;
repeat (2) @(posedge clk);
#1;
if (count !== 4'd3) begin
$display("FAIL: enable hold expected 3, got %0d", count);
$finish;
end
$display("PASS: counter4");
$finish;
end
endmodule
Use four-state comparisons such as !== in testbenches when you want X or Z to fail the test instead of silently matching.
Simulation Timing Sketch
title "Verilog counter simulation"
time start=0 end=70 unit=ns divisions=7
CLK: square label="clk" low=0 high=1 duty=50 cycles=7 unit=logic color=#2563eb
RST: pulse label="rst" low=0 high=1 at=0 width=20 unit=logic color=#dc2626
EN: pulse label="en" low=0 high=1 at=22 width=30 unit=logic color=#7c3aed
COUNT: sawtooth label="count when enabled" min=0 max=3 cycles=1 unit=count color=#16a34a
marker RELEASE at=20 label="reset off"
marker HOLD at=52 label="en low"
This waveform is explanatory. The actual VCD is the authority for your simulation.
Worked Example: Choosing A Tool
For a first counter testbench, Icarus is often enough:
iverilog -g2012 -Wall -o build/counter4_tb.vvp rtl/counter4.v tb/counter4_tb.v
vvp build/counter4_tb.vvp
For a CI check that should reject questionable RTL style, add Verilator:
verilator --lint-only -Wall rtl/counter4.v
That pairing is useful: Icarus proves the testbench behavior, while Verilator highlights synthesizable coding issues before they become toolchain surprises.
Practical Checks
Before trusting a simulation:
- the command starts from a clean build directory;
- the intended top testbench is selected;
- the testbench prints
PASSonly after all checks run; - failures return a nonzero status in scripts;
- warnings are reviewed, not ignored;
- the waveform file timestamp matches the latest run;
- synthesis and timing are still run after simulation.
Common Mistakes
- Compiling files in the wrong order when modules are generated or included.
- Forgetting
$finish, leaving automation stuck. - Checking only that a VCD exists.
- Ignoring width warnings that later change synthesis behavior.
- Driving inputs on the same active edge where the DUT samples them.
- Assuming Verilator and Icarus support identical language subsets and timing semantics.
Summary
Icarus Verilog is a simple event-driven simulator, Verilator is a fast and strict compiled simulator/linter, and GTKWave helps inspect timing after a failure. A solid beginner flow uses simulator checks for pass/fail, waveforms for debugging, and synthesis plus timing analysis for hardware readiness.
Next: GHDL and VHDL Simulation.