Module 6: Verilog for Testbench & Simulation ยท 6.1 Testbench Architecture

Sampling the Outputs

Reading an output should be the easy half. The design computed it; look. But in an event-driven simulation when you look matters as much as what you look at, and several wrong moments look right. Sample at one of them and the testbench reports failures for a working design, which is the most demoralising kind of testbench bug there is.

The race

Section 5.1 covered this from the design side. A non-blocking assignment at a clock edge is scheduled, not applied: q <= d puts the update of q on a list, and the list is applied after every block active at that edge has run. So in the time step of the edge, a testbench block that also runs on posedge clk sees the old q:

// may print last cycle's value
always @(posedge clk_tb) begin
    $display("count = %0d", count_tb);
end

The testbench block and the design’s flip-flops are in the same active region; the update lands afterwards.

Two safe ways

Wait to the middle of the period. After the edge, half a period later, every update has landed and the next edge is far off:

localparam CLK_PERIOD = 10;

always @(posedge clk_tb) begin
    #(CLK_PERIOD/2);
    $display("[%0t] count = %0d", $time, count_tb);
end

Or use $strobe, which runs at the end of the time step, after all the updates, whenever in the step it was called:

always @(posedge clk_tb) begin
    $strobe("[%0t] count = %0d", $time, count_tb);
end

A trace

`timescale 1ns / 1ps

module tb_display_vs_strobe();
    reg       clk;
    reg [3:0] q;

    always @(posedge clk)
        q <= q + 1;

    initial begin
        clk = 0; q = 0;
        #1;

        repeat (4) begin
            @(posedge clk);
            $display("$display at the edge: q = %0d", q);
            $strobe ("$strobe at the edge:  q = %0d", q);
        end

        $finish;
    end

    always #5 clk = ~clk;
endmodule
$display at the edge: q = 0
$strobe at the edge:  q = 1
$display at the edge: q = 1
$strobe at the edge:  q = 2
$display at the edge: q = 2
$strobe at the edge:  q = 3
$display at the edge: q = 3
$strobe at the edge:  q = 4

Same time, different values: $display runs in the active region before the update, $strobe after it. Use $strobe to print the state of clocked logic at an edge and $display for progress messages in the stimulus.

The period

Figure 1 puts the zones together: drive just after the edge; the design updates; sample mid-period; leave the setup window before the next edge alone.

The zones of a clock period
Figure 1: Where to drive and where to sample

Zone When Do
just after the edge edge plus 1 to 2 ns drive inputs
early in the period edge plus 2 to 4 ns the design settles
mid-period half a period after the edge sample outputs
before the next edge last 2 ns leave alone

The adder, sampled

The adder is combinational and has no non-blocking updates to race, so waiting after driving is enough; $strobe costs nothing and keeps working if the design later grows registers:

`timescale 1ns / 1ps

module tb_adder_4bit_sampled();
    reg  [3:0] a_tb, b_tb;
    reg        cin_tb;
    wire [3:0] sum_tb;
    wire       cout_tb;

    adder_4bit dut (.a(a_tb), .b(b_tb), .cin(cin_tb), .sum(sum_tb), .cout(cout_tb));

    integer i, j;

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

        cin_tb = 0;

        for (i = 0; i < 4; i = i + 1) begin
            for (j = 0; j < 4; j = j + 1) begin
                a_tb = i[3:0];
                b_tb = j[3:0];
                #5;
                $strobe("  %0d + %0d = %0d (cout=%b)", a_tb, b_tb, sum_tb, cout_tb);
                #5;
            end
        end

        $finish;
    end
endmodule

The commonest cause of “the testbench says FAIL and the design is right” is a $display of a registered output in the same clocked block, with no delay. Half a period, or $strobe, and it goes away.

Driving and sampling are in place. The last piece turns a testbench that shows things into one that proves them: the self-checking loop.

โ† Driving the Inputs Automatic Pass and Fail โ†’