Chapter 3 of 16
Running Your First Simulation: EDA Playground, Waveforms, and $finish
Actually run a SystemVerilog testbench: set up EDA Playground, understand $timescale and its more modern replacement, timeunit/timeprecision, learn why simulation never ends without $finish, dump and read a waveform with $dumpfile/$dumpvars, and get a complete, copy-pasteable example that works end to end.
The last two chapters covered the "design vs. verification" mental model and the raw syntax needed to read code. But reading isn't running — and HDL is one of the things you learn fastest by actually simulating it. This chapter is entirely about the mechanics of getting a simulation to actually execute: where to run code, how to make it stop, and how to look at what happened.
Where to run code: EDA Playground
You don't need to install a simulator to follow this track. EDA Playground runs one in the browser, for free, with no setup:
- Create a free account (or use the "Continue as guest" option).
- On the left, you'll see two panels: Testbench and (optionally) Design. For everything in this track, one file in the Testbench panel is enough — there's no separate DUT file to manage.
- Under Tools & Simulators, pick a simulator. Any of the free ones (e.g. Icarus Verilog) can run almost every example in this track — nothing here needs a paid license. Two call-outs, so a later surprise doesn't read as a new problem: the UVM track needs a UVM-capable simulator instead (Icarus doesn't support the UVM class library, called out explicitly there), and this track's own
clocking blocksection (ch13) needs a simulator with full SystemVerilog support instead of Icarus (Icarus doesn't implement clocking blocks at all — confirmed directly, not assumed — called out explicitly there too, with a specific alternative to pick). - Check Open EPWave after run so the waveform viewer opens automatically once simulation finishes.
- Paste code into the Testbench panel and click Run.
That's the whole workflow: paste, run, look at the log and the waveform. Every example from here on is written to be pasted directly into that Testbench panel and run as-is.
`timescale: what a "time unit" actually is
Earlier chapters used #10 without ever pinning down what "10" means. `timescale answers that, and it's normally the very first line of a file:
`timescale 1ns/1ps- The first number (
1ns) is the time unit — what#10actually means: 10 of that unit, i.e. 10ns here. - The second number (
1ps) is the time precision — the smallest time increment the simulator tracks internally, used for rounding.
If you omit `timescale, most simulators fall back to a tool-specific default — which is exactly why it's worth setting explicitly rather than leaving it to chance. Every example below starts with `timescale 1ns/1ps, which is a common, sensible default for this kind of teaching example.
timeunit/timeprecision: the more modern way to write this
`timescale is a compiler directive inherited from Verilog: it behaves more like a text macro, applying to every module compiled after that line, up until the next `timescale shows up. If several files with different `timescale settings get compiled together, it's easy for some module to silently end up with the wrong time unit — a classic gotcha.
SystemVerilog offers a more modern alternative: the timeunit/timeprecision keywords, declared directly inside a module/interface/program/package, scoped precisely to that block — unaffected by compile order or other files:
module tb;
timeunit 1ns;
timeprecision 1ps;
// ... same effect as `timescale 1ns/1ps, but scoped to just this module
endmoduleThere's also a compact single-line form with identical meaning:
module tb;
timeunit 1ns/1ps;
// ...
endmodule`timescale is still everywhere — plenty of existing code and tutorials use it, and later examples in this track keep using it for brevity. But new code should generally prefer timeunit/timeprecision: the semantics are identical (first value is the time unit, second is the time precision), just expressed with a keyword form that's scoped clearly and doesn't depend on compile order, avoiding the "who silently changed this in which file" problem `timescale can create — and it's what most UVM codebases actually use today.
A quick note on
program, since it just showed up in that list: every testbench in this track is written inside a plainmodule, the same construct used for a DUT. SystemVerilog also has aprogramblock — same idea asmodule, but historically meant specifically for testbench code, with its own scheduling region (it runs after allmodule-based logic settles for that time step, mainly to sidestep certain race conditions between testbench and DUT). It still shows up in legacy code, but the class-based,virtual interface-driven style this track builds toward (and UVM itself) is written in plainmodules, soprogramisn't used here — worth being able to recognize it if it shows up in someone else's code, not something to reach for yourself.
$finish: why your simulation never seems to end
A simulation doesn't stop on its own just because your initial block reaches its last line — if there's nothing telling the simulator to stop, it will sit there waiting for more events (or the tool's own runtime limit kicks in). $finish is the system task that tells the simulator "stop now":
initial begin
// ... stimulus and checks ...
$finish;
endForgetting $finish is the single most common reason a beginner's first simulation "hangs" or produces no output — the simulation either times out on EDA Playground's server-side limit or just never reaches the point where it would print a summary. Every runnable example in this track ends its top-level initial block with $finish.
$dumpfile / $dumpvars: recording a waveform
$display prints values at the instant you ask, but to see how a signal's value changes over time, you need a waveform. Two system tasks turn that on:
initial begin
$dumpfile("waves.vcd"); // the output file EPWave will open
$dumpvars(0, tb); // dump every signal, starting from module "tb", recursing into everything inside it
end$dumpfile("waves.vcd")names the file the waveform gets written to (EDA Playground's EPWave viewer picks this up automatically — you don't need to open the file yourself).$dumpvars(0, tb)starts recording. The first argument is how many levels deep to recurse (0means "all levels," i.e. everything nested insidetb, however deep); the second is which module to start from.
Place this pair near the top of your top-level initial block, before you start driving signals — anything that happens before $dumpvars runs won't show up in the waveform.
Reading the waveform
Once a simulation with $dumpfile/$dumpvars finishes, EPWave opens showing:
- A signal list on the left — click a signal name to add it to the waveform view (or it may already be added, depending on the viewer's default).
- A timeline across the top, in the units
`timescalespecified. - Colored waveform traces for each signal — for a single-bit signal, a low line is
0, a high line is1, and (for 4-state signals) a distinct color/pattern usually marksx. - A cursor you can click and drag along the timeline to read a specific signal's value at that instant, and a zoom control to expand a busy region or collapse a long idle stretch.
The most common first move: drag the cursor to just after you change an input, and check whether the output actually updated the way you expected.
A complete, runnable example
Here's mux2 from chapter 1, wrapped in an actual testbench with everything above wired in — paste this directly into EDA Playground's Testbench panel and run it as-is:
`timescale 1ns/1ps
module mux2 (
input logic sel,
input logic a,
input logic b,
output logic y
);
always_comb begin
y = sel ? b : a;
end
endmodule
module tb;
logic sel, a, b, y;
mux2 dut (.sel(sel), .a(a), .b(b), .y(y));
initial begin
$dumpfile("waves.vcd");
$dumpvars(0, tb);
sel = 1'b0; a = 1'b1; b = 1'b0;
#10;
$display("t=%0t sel=%0b a=%0b b=%0b y=%0b", $time, sel, a, b, y);
sel = 1'b1;
#10;
$display("t=%0t sel=%0b a=%0b b=%0b y=%0b", $time, sel, a, b, y);
$finish;
end
endmoduleRunning this should print two lines (y following a when sel=0, then following b when sel=1) and open a waveform with four traces (sel, a, b, y) that you can inspect directly. This is also the template worth reusing whenever you want to try out a snippet from a later chapter that isn't already wrapped in a runnable module: drop the snippet's declarations and logic into a tb module's body (or its own class/task, called from initial), add $dumpfile/$dumpvars if you want a waveform, and end with $finish.
Summary
- EDA Playground runs a real simulator in the browser — paste code into the Testbench panel, pick a free simulator (e.g. Icarus Verilog), and click Run.
`timescale <unit>/<precision>(usually the first line of a file) defines what#10-style delays actually mean and how finely the simulator tracks time.timeunit/timeprecisionmean the same thing but are scoped to a singlemodule/interface/program/packageand don't depend on compile order — the more modern, recommended alternative to`timescale.$finishstops the simulation — forgetting it is the most common reason a first simulation appears to hang or print nothing.$dumpfile("name.vcd")+$dumpvars(0, top_module), placed early in the top-levelinitialblock, record a waveform you can inspect after the run.- A waveform viewer shows a signal list, a timeline, and colored traces; drag the cursor to read a specific signal's value at a specific time.
- Any later chapter's bare code snippet can be made runnable by dropping it into a
module tb; ... endmoduleshell with aninitialblock ending in$finish, following the pattern in this chapter's full example.
A simulation seems to just hang forever with no output. What's the most likely cause?
What does `timescale 1ns/1ps mean?
What's the main advantage of timeunit/timeprecision keywords inside a module over the `timescale compiler directive?
Which system task tells the simulator to stop running? (with the leading $, lowercase)