Module 6: Verilog for Testbench & Simulation ยท 6.3 Generating Clocks & Reset Sequences

Initialisation

At time zero every reg is x, and the simulator carries x through every gate and mux honestly. Leave one input undriven and x spreads like ink in water, and the failure is quiet: the run finishes, the checker prints FAIL on everything, or, with the wrong comparison operator, PASS on everything because it stopped comparing anything real.

One undriven input

module mux4to1 (
    input  wire [3:0] d,
    input  wire [1:0] sel,
    output reg        y
);
    always @(*) begin
        case (sel)
            2'b00: y = d[0];
            2'b01: y = d[1];
            2'b10: y = d[2];
            2'b11: y = d[3];
        endcase
    end
endmodule
module tb_mux4to1();
    reg [3:0] d;
    reg [1:0] sel;
    wire      y;

    mux4to1 dut (.d(d), .sel(sel), .y(y));

    initial begin
        d = 4'b1010;        // sel is never driven
        #10;
        $display("sel=%b  d=%b  y=%b", sel, d, y);
        $finish;
    end
endmodule
sel=xx  d=1010  y=x

The mux does not know which input to pick, so the output is x. A checker using !== reports a failure; one using != gets x from the comparison, treats it as false, and reports a pass. Either way the result means nothing. When a waveform is full of x, or a testbench fails no matter what the design does, check that every input is driven before the first edge before you look at the design.

The order

  1. Declare every driven signal as reg, with an inline value as a backstop: reg [7:0] data = 0;.
  2. At the top of the initial block, assign every input its idle value explicitly, reset asserted.
  3. Let the clock run; the design ignores it while in reset.
  4. Hold reset for RESET_CYCLES edges.
  5. Release it 2 ns after an edge.
  6. Wait one or two more edges for state machines to reach idle and registered outputs to show their reset values.
  7. Then, and only then, start the tests.

Inline and procedural

reg clk = 0;            // inline

reg clk;
initial clk = 0;        // procedural

Both give the value before any event runs. The procedural form is easier to audit, because the whole start-up is in one block at the top of the file; the inline form is a backstop in case an assignment is later dropped from that block. Use both:

reg [7:0] data  = 8'h00;
reg       valid = 1'b0;

initial begin
    data  = 8'h00;
    valid = 1'b0;
    // ...
end

Complete

`timescale 1ns / 1ps

module tb_reg8();

    parameter CLK_PERIOD   = 10;
    parameter RESET_CYCLES = 4;

    reg        clk     = 1'b0;
    reg        rst_n   = 1'b0;
    reg [7:0]  data_in = 8'h00;
    reg        wr_en   = 1'b0;
    wire [7:0] data_out;

    reg8 dut (.clk(clk), .rst_n(rst_n), .data_in(data_in), .wr_en(wr_en), .data_out(data_out));

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

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

        rst_n   = 1'b0;                          // idle values, reset asserted
        data_in = 8'h00;
        wr_en   = 1'b0;

        repeat (RESET_CYCLES) @(posedge clk);    // hold
        #2;
        rst_n = 1'b1;                            // release

        @(posedge clk);                          // settle
        @(posedge clk);

        $display("ready at %0t ns, data_out=%h", $time, data_out);

        @(posedge clk); #2;                      // write 0xAB
        data_in = 8'hAB;
        wr_en   = 1'b1;
        @(posedge clk); #2;
        wr_en   = 1'b0;
        @(posedge clk);
        $display("after write: data_out=%h (expected AB)", data_out);

        rst_n = 1'b0;                            // reset clears it
        @(posedge clk);
        #2;
        rst_n = 1'b1;
        @(posedge clk);
        $display("after reset: data_out=%h (expected 00)", data_out);

        $finish;
    end

endmodule

Driving x on purpose

Sometimes you want an x on an input, to see what the design does with an illegal or unknown value:

signal = 1'bx;
bus    = {8{1'bx}};

A mode input that allows 00, 01 and 10 should be tried at 11, and at xx:

@(posedge clk); #2;
mode = 2'b11;
@(posedge clk);
$display("illegal mode: error_flag=%b (expected 1)", dut.error_flag);

@(posedge clk); #2;
mode = 2'bxx;
@(posedge clk);
$display("unknown mode: error_flag=%b", dut.error_flag);

An x out for an x in may be acceptable. A design that stays corrupted afterwards, on valid inputs, is a bug to find here rather than on a board.

x in a printout

reg [7:0] val = 8'hxx;
$display("%b", val);    // xxxxxxxx
$display("%h", val);    // xx
$display("%d", val);    // x

reg [7:0] val2 = 8'b1010_xx10;
$display("%b", val2);   // 1010xx10
$display("%h", val2);   // ax

Learn to see an x in output as a pointer: something was not initialised, or the design drove something undefined, and both are worth chasing.

The last article of this section is the case where the design has two clocks and the testbench has to know which one governs which signal.

โ† Reset in the Testbench Two Clocks โ†’