One generator drives the whole testbench. The period lives in a single definition.
// snippet: two common ways to generate a clock
timeunit 1ns; timeprecision 1ps; // first items in the testbench module
// Keep the period in ONE place (a parameter or localparam),
// declared before use, and derive any related delays from it.
localparam time PERIOD = 10ns;
always #5 clk = ~clk; // simplest; needs 'clk' initialized to 0
initial begin // explicit form, easy to change
clk = 0;
forever #(PERIOD/2) clk = ~clk;
endalways #5 clk = ~clk; without an initial value a bug?clk starts as x, and ~x is x, so the clock never toggles. Initialize it (logic clk = 0;) before the always block runs.
Changing an input at the clock edge creates a race. Whether the DUT sees the old or the new value then depends on simulator ordering.
That ordering can differ between simulators, so drive inputs away from the active edge.
Strategy | How | Notes |
|---|---|---|
Drive after a small delay |
| Simple and clear; matches the sampling rule from the last lesson. |
Drive on the opposite edge |
| Inputs are stable half a period before the active edge. |
Non-blocking in the testbench |
| Update lands after the DUT samples, like a real flop. |
Clocking block (SystemVerilog) |
| The standard solution in larger environments; sampling skew is built in. |
Each slot is half a clock period, sampled just after a clock edge: clk high means the slot follows a rising edge, low means it follows a falling edge.
Slot 1: the bench changes d to A5 at the falling edge. q still holds 00, because nothing samples d on a falling edge.
Slot 2: the rising edge captures A5. d has been stable for half a period, so there is no race about which value the flop sees.
Slots 3 and 4: the same pattern for 3C: d changes in slot 3, q follows one slot later in slot 4.
Assert reset at the start, and hold it for several clocks so every flop (including slow deep ones) sees it.
Create a real 1 → 0 transition for an asynchronous active-low reset. A declaration initialiser such as logic rst_n = 0 holds 0 from time 0, so no falling edge occurs. A first procedural assignment of 0 to a signal that is still x is an x-to-0 transition and does count as a falling edge, but it hides the intent. Start from a known 1 (logic rst_n = 1) and drive to 0 after #1 so the edge is explicit and visible in the waveform.
Release reset away from the clock edge, as you would in hardware: after a #1 or on the opposite edge.
Do not apply stimulus while reset is asserted unless the test is about reset itself.
Test reset in the middle of operation too, not only at power-up.
Inputs driven on the opposite edge are captured without races.
module reg8 (input logic clk, input logic [7:0] d, output logic [7:0] q);
always_ff @(posedge clk) q <= d;
endmodule
module tb_drive_timing;
timeunit 1ns; timeprecision 1ps;
logic clk = 0;
logic [7:0] d, q;
int errors = 0;
reg8 dut (.clk, .d, .q);
always #5 clk = ~clk;
initial begin
d = 8'h00;
@(negedge clk) d = 8'hA5; // driven half a cycle before the edge
@(posedge clk); #1;
if (q !== 8'hA5) errors++; // captured exactly once, no race
@(negedge clk) d = 8'h3C;
@(posedge clk); #1;
if (q !== 8'h3C) errors++;
if (errors != 0) $fatal(1, "FAIL: opposite-edge drive errors=%0d", errors);
$display("PASS: inputs driven on the opposite edge are captured cleanly");
$finish;
end
endmodule