1. Choosing what to record
$dumpfile names the VCD file and $dumpvars(0, tb) records everything below tb from the moment
it runs; both were introduced in
Register stages, observation and waveforms.
This chapter chooses what to record when everything is too much, and collects the mistakes that
leave a waveform empty, late or incomplete.
1.1 Scopes and depth
The scope arguments of $dumpvars choose which instances to record, and the depth sets how many
levels below each one are included. You may call it several times; the selections add up.
Syntax
$dumpvars(depth, scope);
$dumpvars(depth, scope); // a second call adds to the selection| Call | Recorded |
|---|---|
$dumpvars(0, tb) | tb and every instance below it |
$dumpvars(1, tb) | Only the signals declared in tb itself |
$dumpvars(2, tb) | tb and its direct children, such as tb.dut |
$dumpvars(0, tb.dut) | tb.dut and everything below it, but not tb |
$dumpvars with no arguments | Everything in the simulation |
A scope is a hierarchical name: instance names joined by dots, as in tb.dut.div.
Tip. Large designs produce large VCD files, and a waveform with hundreds of signals is hard to read. Every change is written to the file and, in this lab, sent to your browser, so record only the part you are investigating.
1.2 Example: skipping a level
Blinker.v has two levels. divider counts a 2-bit phase and raises tick for one cycle in
every four. blinker instantiates divider as div and toggles led on every tick.
// tick is high for one cycle in every four.
module divider (
input clock,
input reset,
output tick
);
reg [1:0] phase;
always @(posedge clock) begin
if (reset) phase <= 2'd0;
else phase <= phase + 2'd1;
end
assign tick = (phase == 2'd3);
endmodule
// The LED toggles on every tick from the divider.
module blinker (
input clock,
input reset,
output reg led
);
wire tick;
divider div (.clock(clock), .reset(reset), .tick(tick));
always @(posedge clock) begin
if (reset) led <= 1'b0;
else if (tick) led <= ~led;
end
endmoduleTb.v calls $dumpvars twice: once for tb at depth 1, once for tb.dut.div at depth 1. It also
prints a LED line whenever led changes.
module tb;
reg clock = 0;
reg reset = 1;
wire led;
blinker dut (.clock(clock), .reset(reset), .led(led));
always #5 clock = ~clock;
always @(led) $display("LED t=%0t led=%b", $time, led);
initial begin
$dumpfile("dump.vcd");
// Depth 1: only the signals declared directly in tb.
$dumpvars(1, tb);
// A second call adds one more scope, here the divider two levels down.
$dumpvars(1, tb.dut.div);
@(negedge clock) reset = 0;
repeat (12) @(posedge clock);
#1 $finish;
end
endmodulePractice this concept
Use this concept in a short Verilog problem as your next step.
Predict before you run.
- Which scopes appear in the waveform?
- Are
blinker's own signals,tickand the registerled, recorded under atb.dutscope? - At which times are the
LEDlines printed?
Check
Tb.v records tb at depth 1 and tb.dut.div at depth 1. Does a tb.dut scope with tick and the register led appear in the waveform?
Check the result.
| Scope | Recorded signals |
|---|---|
tb | clock, reset, led |
tb.dut | None: no call selected this scope |
tb.dut.div | The ports clock, reset, tick and the register phase |
The LED lines appear at time 5, when the reset value is set, and at 45, 85, and 125, the rising
edges right after phase reaches 3. In the waveform phase steps 0, 1, 2, 3 at rising edges,
tick is high exactly while phase is 3, and led toggles at the edge that ends each tick. The
divider's clock port is the same net as tb's clock, so the clock is shown once, under tb.
Try it yourself. Replace the second call with $dumpvars(0, tb.dut). Now a tb.dut scope
appears holding tick and the register led, with tb.dut.div below it. The LED lines do not
change at all: what you record never changes what is simulated.
2. Common mistakes
2.1 Never calling $dumpvars
// Wrong: names the file but never selects anything to record
initial begin
$dumpfile("dump.vcd");
// ...
end
// Right
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, tb);
// ...
endWithout $dumpvars, Icarus does not create a VCD file at all. The run succeeds, but there is no
VCD info: line and no waveform.
2.2 Starting the recording late
// Wrong: recording starts after reset is over
repeat (2) @(posedge clock);
$dumpvars(0, tb);
// Right: recording starts at time 0
$dumpvars(0, tb);
repeat (2) @(posedge clock);$dumpvars records from the moment it runs. Call it late and the most useful part for debugging,
the initial x values and the reset sequence, is missing from the file.
Check
The clock is always #5 clock = ~clock starting at 0, and the wrong version above calls $dumpvars after repeat (2) @(posedge clock). At what time does the recording start?
2.3 A depth too small to reach the signal
// Wrong: only tb's own signals are recorded; nothing inside tb.dut
$dumpvars(1, tb);
// Right: record every level below tb
$dumpvars(0, tb);If a signal you need is missing from the waveform, check that its scope is inside the selection and that the depth reaches that level.
Check
In the Blinker example, phase is declared in tb.dut.div. What is the smallest positive depth d for which a single $dumpvars(d, tb) records it?
2.4 Fractional delays without `timescale
// Wrong: the precision is one whole unit, so 10.5 rounds to 11
#10.5 b = 0;
// Right: put the directive at the top of every file
`timescale 1ns / 1ps
// ...
#10.5 b = 0;Without a `timescale, fractional delays silently move to whole times. If the times in a
waveform are slightly off from what you expected, check the directive first. The unit and precision
it sets are in
Register stages, observation and waveforms.
2.5 Looking for a value that exists only inside an expression
// Hard to observe: a & b has no name, so it is not in the VCD
always @(posedge clock) y_reg <= a & b;
// Easy to observe: give the intermediate value a wire
wire both = a & b;
always @(posedge clock) y_reg <= both;A VCD records declared signals only. To see an intermediate result in the waveform, declare it as a
wire. The hardware is unchanged, and the waveform gains one signal.
3. Summary
| Topic | Key point |
|---|---|
| Recording scope | Depth 0 covers all levels below, 1 only the scope itself. Several calls add up |
| Scope name | Instance names joined by dots, as in tb.dut.div; a scope no call names is not recorded, even between two recorded ones |
No $dumpvars | No file at all: the run succeeds without a VCD info: line or a waveform |
Late $dumpvars | The file starts where the call ran; the initial x and the reset sequence are missing |
Fraction without `timescale | Rounded to a whole unit, so times shift silently |
| Unnamed expression | Not in the VCD; give it a wire to see it |
The Practice problem delay-line below this chapter is a two-stage delay line that shows the same
value in its first stage and its output stage on every line, behaving like a single register.
Compare in the waveform which edges each stage changes on to find the cause, and record deep enough
to see both stages.
4. Check your understanding
-
How would you call
$dumpvarsto recordtb.dutbut nottb.dut.div?Show answer
Record
tb.dutat depth 1 with$dumpvars(1, tb.dut), adding$dumpvars(1, tb)if you also wanttb's signals. A single$dumpvars(2, tb)gets the same result, because it recordstbandtb.dutbut nottb.dut.div.