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