How Verilog simulation works
Unlike standard software executed step-by-step on a CPU, hardware logic evaluates concurrently across clock cycles. This guide breaks down discrete event queues, testbench writing, VCD waveform internals, and how browser-based execution runs in the cloud.
1. The event-driven simulation queue
Verilog simulators like Icarus Verilog use a discrete event scheduler. Time advances only when an active event occurs (a clock edge rises, a wire toggles state), rather than calculating every picosecond continuously.
-
Active Region: Evaluates continuous assignments (
assign), blocking statements (=), and$displayroutines. -
Inactive Region: Evaluates statements delayed by zero time units (
#0). Avoid using#0in synthesizable RTL. -
NBA (Non-Blocking Assignment) Region: Evaluates registers assigned
with
<=. This ensures all flip-flops sample their inputs simultaneously on a clock edge before updating their output registers. -
Postponed Region: Samples variables for trace generation
(
$dumpvars) and concurrent assertions without logging intermediate glitches.
2. Anatomy of a Verilog testbench
A testbench is a verification harness that contains no input or output ports. It instantiates your module (the DUT, Device Under Test), generates clocks and resets, applies input stimuli, and records the output response.
| DUT Port Type | Testbench Declaration | Reason |
|---|---|---|
input |
reg (or logic) |
The testbench actively updates values inside procedural blocks (initial / always). |
output |
wire |
The output is driven by the DUT and read passively by the testbench. |
Shift register & testbench example
Here is an 8-bit serial-in, parallel-out (SIPO) shift register alongside a self-contained verification testbench:
// Synthesizable Design Under Test (DUT)
module shift_reg_8bit (
input wire clk,
input wire rst_n,
input wire shift_en,
input wire din,
output reg [7:0] dout
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout <= 8'h00;
end else if (shift_en) begin
dout <= {dout[6:0], din};
end
end
endmodule
// Simulation Testbench
`timescale 1ns/1ps
module tb_shift_reg;
reg clk;
reg rst_n;
reg shift_en;
reg din;
wire [7:0] dout;
shift_reg_8bit dut (
.clk(clk), .rst_n(rst_n),
.shift_en(shift_en), .din(din), .dout(dout)
);
// 100 MHz clock (10ns period)
always #5 clk = ~clk;
initial begin
$dumpfile("waveform.vcd");
$dumpvars(0, tb_shift_reg);
clk = 0; rst_n = 0; shift_en = 0; din = 0;
#12 rst_n = 1; // release async reset
@(negedge clk); shift_en = 1; din = 1;
@(negedge clk); din = 0;
@(negedge clk); din = 1;
@(negedge clk); din = 1;
@(negedge clk); shift_en = 0; din = 0;
#20;
$finish;
end
endmodule
3. How waveforms are captured: The VCD format
When running tools like Icarus Verilog (iverilog + vvp),
transition data is recorded in Value Change Dump (VCD) format, defined
by IEEE 1364:
-
Header Section: Declares the simulation timescale (
$timescale 1ns$end), module scopes, and variable mapping tokens. -
Change Section: VCD records only value changes. If a bus remains
at
0xAAfor 500 clock cycles, no entries are recorded until the next transition.
0 (Low/GND), 1 (High/Vdd), X (Unknown / uninitialized register),
and Z (High Impedance / floating net).
4. Common simulation pitfalls
-
Missing
$finish: If your testbench omits$finish;, the clock will toggle indefinitely until backend servers enforce an execution timeout. -
Stimulus race conditions: Changing input variables directly on
@(posedge clk)can lead to non-deterministic sampling across different simulators. Always apply stimulus on@(negedge clk)or introduce a slight propagation offset (e.g.#1 din = 1;). -
Inferred latches: Combinational blocks with incomplete conditional
assignments (e.g., an
ifwithout anelsebranch) tell the synthesizer to preserve stale values, creating asynchronous latches.
Frequently asked questions
RTL Bench compiles HDL source code using Icarus Verilog (iverilog -g2012) in isolated cloud containers. The output executable (vvp) generates a Value Change Dump (VCD) file, which is streamed back to your browser and visualized interactively without installing local software.
Yes. If no module containing $dumpfile or $dumpvars is detected, RTL Bench automatically analyzes the top-level module ports, synthesizes a dynamic testbench, drives clock and reset sequences, and supplies randomized input stimulus.
Yes. You can upload multiple .v and .sv files, header files (.vh, .svh), and memory initialization files referenced through $readmemh or $readmemb.