FPGA

Simulate Verilog with Icarus Verilog

By CourseTron Team ·  3 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: Icarus Verilog compiles your RTL and testbench with iverilog, runs the result with vvp, and dumps a VCD file you open in GTKWave; internally it is an event-driven simulator that follows the same scheduling regions (active, inactive, non-blocking update) that Synopsys VCS, Cadence Xcelium and Siemens Questa implement.

    Why Icarus Verilog is worth learning

    Icarus Verilog (iverilog) is a free, open-source compiler and simulator that supports most of IEEE 1364-2005 and a growing slice of SystemVerilog (IEEE 1800). It costs nothing, installs in a minute on Linux, macOS or Windows, and runs in CI containers with no licence server. For FPGA learners that matters: you can validate a design before ever opening a vendor tool. If you are new to the platform itself, start with what an FPGA is, then come back here.

    Key fact: Icarus is a simulator, not a synthesiser. A design that simulates cleanly can still fail synthesis, and the reverse is also true, so treat it as the first gate, not the last.

    The three-step flow: compile, run, view

    Unlike GUI simulators that hide the steps, Icarus exposes them. iverilog parses and elaborates the sources and writes a vvp assembly file. vvp is the runtime that executes it. Your testbench decides what is recorded, using $dumpfile and $dumpvars. GTKWave then reads the dump.

    Icarus Verilog toolchain dut.v + tb.v source files iverilog compile + elaborate vvp sim.vvp run simulation GTKWave view wave.vcd sim.vvp $dumpvars CourseTron · coursetron.com
    The toolchain: two commands produce a dump file, a third program displays it.

    A minimal counter testbench shows the whole loop:

    `timescale 1ns/1ps
    module tb;
      reg clk = 0, rst = 1;
      wire [3:0] q;
      counter dut(.clk(clk), .rst(rst), .q(q));
      always #5 clk = ~clk;          // 100 MHz
      initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb);
        #22 rst = 0;
        #100 $finish;
      end
    endmodule
    iverilog -g2012 -Wall -o sim.vvp tb.v counter.v
    vvp sim.vvp
    gtkwave wave.vcd

    Always pass -Wall: it flags implicit nets and port-width mismatches that otherwise silently produce x values. The -g2012 flag enables SystemVerilog syntax. Note the timescale directive: without it, delays are in default units and your 10 ns clock may display as 10 s of nothing useful. Also revisit reg versus wire if the compiler complains about driving a net from a procedural block.

    Key fact: $dumpvars(0, tb) dumps every signal below tb. Level 0 means unlimited depth; on a large SoC that file can reach gigabytes, so dump selectively, or use vvp sim.vvp -fst for the compact FST format.

    How Icarus maps to the standard event model

    Verilog simulation is discrete-event: the kernel keeps a queue of time slots, and in each slot it works through ordered regions until no events remain, then advances time. This is defined in the language standard, which is why the same RTL gives the same cycle-level behaviour on Icarus, VCS, Xcelium and Questa.

    One simulation time slot Active: blocking =, assigns, evaluate RHS Inactive: #0 delays NBA: apply non-blocking updates new events? loop back to Active Empty: $strobe, then advance time CourseTron · coursetron.com
    Active, inactive and non-blocking regions repeat until the slot is empty; only then does time move.

    Icarus implements this as follows. Each always and initial block becomes a thread in vvp. A thread runs until it hits a delay or an event wait, then sleeps on the scheduler. A blocking assignment updates its target immediately within the thread. A non-blocking assignment queues an update that vvp applies only after the active and inactive queues drain, which is exactly the NBA region. That is why a flop written as q <= q + 1 reads the old value of q in every other block triggered by the same clock edge. The full reasoning is in blocking versus non-blocking assignments.

    The bigger SystemVerilog scheduler has extra regions (observed, reactive, re-NBA, postponed) for assertions and program blocks. Icarus supports only a subset of these, so assertion-heavy, UVM-style verification still belongs on a commercial tool.

    Watch it happen: a counter ticking

    clk and q[3:0] as GTKWave shows them clk q 0123 each rising edge: q updates in the NBA region Red line = simulation time cursor CourseTron · coursetron.com
    The cursor sweeps across; the counter value changes only just after each rising clock edge.

    Reading waveforms like an engineer

    In GTKWave, drag signals from the hierarchy tree into the signal pane, set the radix to hex or decimal, and use the marker to measure edge-to-edge time. Three habits save hours:

    • Look for X first. Red or labelled x after reset means an uninitialised register or an undriven input. Count how many cycles reset is held.
    • Check the clock period. With #5 clk = ~clk and a 1 ns / 1 ps timescale you get a 10 ns period. Measuring it confirms the timescale took effect.
    • Save a .gtkw file so signal selection and radix survive each re-run.
    ToolTypeTypical use
    Icarus VerilogEvent-driven, open sourceLearning, small blocks, CI regressions
    VerilatorCycle-based, compiles to C++Fast RTL models, 2-state only
    VCS / Xcelium / QuestaEvent-driven, commercialFull SystemVerilog, UVM, gate-level with SDF

    Common pitfalls and how to avoid them

    • Race at time zero. If two initial blocks both touch a signal at t=0, order is undefined. Drive testbench inputs away from the clock edge, for example on the falling edge.
    • No $finish. A free-running clock keeps the queue non-empty forever, so vvp never exits.
    • Wrong top module. With several uncalled modules, name the root: iverilog -s tb.
    • Bit-width surprises. Mixed widths are padded or truncated quietly; see sign extension in Verilog for the signed case.
    • Gate-level limits. Icarus can read SDF annotation only in a limited way, so post-layout timing sign-off should use a commercial simulator alongside PrimeTime or Tempus.

    Key fact: If a result differs between Icarus and VCS, suspect a race in your code first. A correct, race-free design behaves identically on both.

    Want guided practice on flows like this? ChipXpert's VLSI training programs pair simulation labs with real tool flows.

    Ready to go from first simulation to a working FPGA design? Explore the hands-on CourseTron courses and build testbenches with mentor feedback.

    FAQ

    Does Icarus Verilog support SystemVerilog?

    Partly. With -g2012 it handles many synthesizable constructs, logic types, interfaces in basic form and some assertions, but not full UVM or constrained-random classes.

    Why is my VCD file empty?

    Usually $dumpvars runs after the signals stop changing, or the dump call sits in a module that is not the root. Place it in an initial block at time zero in the top-level testbench.

    Can I use GTKWave with FST instead of VCD?

    Yes. Run vvp sim.vvp -fst; GTKWave opens the smaller, faster FST file directly.

    Is Icarus accurate enough for FPGA work?

    For functional RTL verification, yes. Timing closure is a separate job done in the vendor tools; see the Xilinx versus Intel FPGA comparison for those flows.

    Advertisement