Module 6: Verilog for Testbench & Simulation ยท 6.5 Waveform Analysis

VCD Files

A self-checking testbench says what failed. This section is about seeing why, and it starts with the recording. A VCD file logs every change of every recorded signal with a timestamp; GTKWave plays it back. The simulation has finished by the time you look; the file is the tape. So there are two jobs: record the right signals for the right window, then read the result.

Two lines

`timescale 1ns / 1ps

module tb_alu();
    reg  [7:0] a, b;
    reg  [2:0] op;
    wire [7:0] result;
    wire       overflow;

    alu dut (.a(a), .b(b), .op(op), .result(result), .overflow(overflow));

    initial begin
        $dumpfile("alu_sim.vcd");
        $dumpvars(0, tb_alu);

        a = 8'd10;  b = 8'd20; op = 3'd0; #20;
        a = 8'd255; b = 8'd1;  op = 3'd0; #20;
        a = 8'd15;  b = 8'd15; op = 3'd1; #20;
        $finish;
    end
endmodule

$dumpfile names the file, relative to where the simulator runs, in a directory that already exists. $dumpvars(depth, scope) says what to record: the scope is where to start, the depth is how many levels below it to include, with 0 meaning all of them. $dumpvars(0, tb_alu) records everything in the simulation.

Both calls go first in the initial block, before any stimulus; recording starts when $dumpvars runs and nothing before it is kept.

Keeping it small

A large design changes tens of thousands of signals a cycle and a full dump is gigabytes. Several $dumpvars calls pick what matters:

initial begin
    $dumpfile("targeted_sim.vcd");
    $dumpvars(1, tb_top.dut);              // the design's own signals, no children
    $dumpvars(0, tb_top.dut.alu_block);    // one block in full
    $dumpvars(1, tb_top.dut.state_reg);    // one signal
end

The top of the design shallowly and the suspect block deeply is the usual shape.

Windows

$dumpoff suspends recording, $dumpon resumes it, and $dumpall writes a snapshot of every recorded signal so that the viewer knows their values after a gap:

initial begin
    $dumpfile("windowed.vcd");
    $dumpvars(0, tb_top);
    $dumpoff;                        // not interested in initialisation

    repeat (100) @(posedge clk);

    $dumpon;                         // the part that matters
    $dumpall;
    send_packet();
    $dumpoff;

    repeat (10000) @(posedge clk);
    $finish;
end

In a long run this cuts the file by an order of magnitude.

Inside the file

VCD is plain text, and readable once you know the two halves:

$timescale 1ns $end

$scope module tb_counter $end
$var reg  1 ! clk $end
$var reg  1 " rst_n $end
$var reg  4 # count [3:0] $end
$var wire 1 $ done $end
$upscope $end
$enddefinitions $end

#0
0!
1"
b0000 #
0$
#5
1!
#10
0!
#15
1!
b0001 #
#25
1!
b0010 #

The header gives each signal a short code, ! for clk and so on. The rest is timestamps, lines starting with #, and changes: 0! is the clock going low, b0001 # is the count becoming 1. That is all GTKWave is parsing. If it opens a file and shows no signals, the header is empty, which usually means $dumpvars was never reached, and thirty seconds in a text editor tells you so.

FST

VCD is universal and verbose. Icarus can write FST, a compressed binary format an order of magnitude smaller that GTKWave opens much faster, by running the simulation with vvp sim -fst and leaving the testbench unchanged. The commercial simulators have their own formats, FSDB and the like, with the same idea. Use VCD to learn and FST when the files get large.

A testbench with selective dumping

`timescale 1ns / 1ps

module tb_counter_vcd();
    parameter CLK_PERIOD = 10;

    reg        clk;
    reg        rst_n;
    wire [3:0] count;
    wire       done;

    counter_4bit dut (.clk(clk), .rst_n(rst_n), .count(count), .done(done));

    initial clk = 0;
    always #(CLK_PERIOD / 2) clk = ~clk;

    initial begin
        $dumpfile("counter_vcd.vcd");
        $dumpvars(1, tb_counter_vcd);         // the testbench's own signals
        $dumpvars(0, tb_counter_vcd.dut);     // the design in full

        rst_n = 0;
        repeat (3) @(posedge clk); #2;
        rst_n = 1;

        repeat (20) @(posedge clk);

        $display("done at %0t ns", $time);
        $finish;
    end
endmodule

With the recording made, the next article is the viewer.

โ† Directed Tests Getting Around GTKWave โ†’