Skip to content
code0.HDL

Recording waveforms

Choose which instances a VCD records with $dumpvars scopes and depths, read a waveform whose scopes skip a level, and recognize the mistakes that leave a recording empty, late, too shallow, off by a rounded delay, or missing an unnamed value.

In this chapter

  • Choose which instances a VCD records with $dumpvars scopes and depths, adding calls when one selection is not enough
  • Read a waveform whose scopes skip a level and explain why a signal is missing from it
  • Recognize the five recording mistakes: no $dumpvars, a late start, a depth too small, a fractional delay without `timescale, a value that exists only inside an expression
Concepts in this chapter
  • Recording waveforms
Prerequisite concepts

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
CallRecorded
$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 argumentsEverything 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.

Blinker.v
// 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
endmodule

Tb.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.

Tb.v
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
endmodule

Practice this concept

Use this concept in a short Verilog problem as your next step.

Predict before you run.

  1. Which scopes appear in the waveform?
  2. Are blinker's own signals, tick and the register led, recorded under a tb.dut scope?
  3. At which times are the LED lines 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?

Your answer

Check the result.

ScopeRecorded signals
tbclock, reset, led
tb.dutNone: no call selected this scope
tb.dut.divThe 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);
  // ...
end

Without $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

TopicKey point
Recording scopeDepth 0 covers all levels below, 1 only the scope itself. Several calls add up
Scope nameInstance names joined by dots, as in tb.dut.div; a scope no call names is not recorded, even between two recorded ones
No $dumpvarsNo file at all: the run succeeds without a VCD info: line or a waveform
Late $dumpvarsThe file starts where the call ran; the initial x and the reset sequence are missing
Fraction without `timescaleRounded to a whole unit, so times shift silently
Unnamed expressionNot 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

  1. How would you call $dumpvars to record tb.dut but not tb.dut.div?

    Show answer

    Record tb.dut at depth 1 with $dumpvars(1, tb.dut), adding $dumpvars(1, tb) if you also want tb's signals. A single $dumpvars(2, tb) gets the same result, because it records tb and tb.dut but not tb.dut.div.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows