Module 6: Verilog for Testbench & Simulation ยท 6.2 Simulation-Only Constructs

#delay, @(event) and wait()

Time in a simulation does not pass by itself. Between events the simulator is frozen until some process yields and says when to resume, and there are exactly three ways to do that: # for a fixed delay, @ for an edge, and wait for a condition. Each means something different, and the wrong one gives a testbench that is fragile, rigid, or quietly wrong.

#delay

#10;
#(CLK_PERIOD * 3);

Advance by a fixed amount. Right for things that are genuinely times: a reset held for so many nanoseconds, a pulse of a given width, the small offset after an edge before driving.

initial begin
    rst_n = 0;
    #100;
    rst_n = 1;

    wr_en = 1;
    #50;
    wr_en = 0;
end

Wrong for counting clock cycles. #10 meaning “one cycle” because the period happens to be 10 breaks the day someone changes the period, and that is what @(posedge clk) is for.

@(event)

@(posedge clk);
@(negedge clk);
@(data_valid);                   // any change
@(posedge a or negedge b);       // whichever comes first

Suspend until a transition. @(posedge clk) is how a testbench waits one cycle, and repeat (5) @(posedge clk) is five, and both stay right whatever the period is. Without posedge or negedge the wait is for any change. Several events go together with or, or with a comma in Verilog-2001 and later.

wait()

wait (data_valid);
wait (fifo_empty == 0);
wait (ack || timeout);

Suspend until an expression is true, and this is the difference that matters: wait checks now. If the condition already holds, it continues at once. @(posedge done) waits for a rising edge, and if done rose before the testbench got there, it waits forever. wait (done) asks whether done is high and carries on if it is.

So: @(posedge clk) for clock synchronisation, where the next edge is always ahead of you; wait (ready) or wait (done) for handshakes, where the signal may already be up.

The fraction-of-a-period offset

// fragile
#1 data_in = new_value;      // 1 ns after what, exactly?

// robust
@(posedge clk);
#(CLK_PERIOD * 0.1);         // a tenth of a period after the edge
data_in = new_value;

An absolute #1 can land on an edge when the period or the timescale changes, which is the race from section 6.1. An offset written as a fraction of the period scales with it.

Figure 1 puts the three side by side against a clock and a handshake.

Three ways to wait
Figure 1: A delay, an edge, and a condition

All three together

`timescale 1ns / 1ps

module tb_mem_ctrl();

    parameter CLK_PERIOD   = 10;
    parameter RESET_CYCLES = 4;
    parameter MAX_SIM_TIME = 50_000;

    reg        clk, rst_n;
    reg        req;
    reg  [7:0] addr;
    reg  [7:0] wdata;
    wire [7:0] rdata;
    wire       ack;

    // mem_ctrl dut ( ... );

    initial begin
        #MAX_SIM_TIME;
        $fatal(1, "TIMEOUT at %t", $time);
    end

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

    initial begin
        $dumpfile("tb_mem_ctrl.vcd");
        $dumpvars(0, tb_mem_ctrl);
        $timeformat(-9, 0, " ns", 8);

        // reset: a duration, so a delay, written in periods to stay portable
        rst_n = 0;
        req   = 0;
        addr  = 0;
        wdata = 0;
        #(CLK_PERIOD * RESET_CYCLES);

        // release on a falling edge, well clear of the rising one
        @(negedge clk);
        rst_n = 1;
        $display("reset released at %t", $time);

        // two cycles to settle: edges
        repeat (2) @(posedge clk);
        #(CLK_PERIOD * 0.1);

        req  = 1;
        addr = 8'hAB;
        $display("read request at %t: addr=%h", $time, addr);

        // the acknowledge comes when the design is ready: a condition
        @(posedge clk);
        wait (ack);
        $display("ack at %t: rdata=%h", $time, rdata);

        req = 0;
        @(posedge clk);
        #(CLK_PERIOD * 0.1);

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

endmodule

The reset is a duration and uses a delay, written as periods. Reset release and the settling cycles are edges. The acknowledge arrives whenever the design decides, so it is a condition.

Waiting for Use
a fixed-width pulse or reset #delay
N clock cycles repeat (N) @(posedge clk)
a moment after an edge to drive @(posedge clk); #(CLK_PERIOD * 0.1)
a handshake or done flag wait (signal)
an edge you know is still ahead @(posedge signal)
either of two signals @(sig_a or sig_b)
combinational logic to settle a small #delay

Team guidelines usually forbid bare #N in clock-synchronous sequences altogether and allow it only for resets, offsets and glitch generation, where a number of nanoseconds is what is meant.

That completes the simulation-only toolkit. The next section is the infrastructure every clocked testbench needs: clocks and resets, and the start-up sequence that models what silicon sees.

โ† $readmemh and $readmemb Generating Clocks โ†’