The test changes control signals on a falling edge and checks on a falling edge. They are stable before the next active edge, and nonblocking assignments have been applied before the read.
Understanding one simulation time step
Several events can share one simulation time. On a rising edge:
blocks sensitive to that edge are activated;
the right-hand side of each nonblocking assignment is evaluated;
new values are applied later in the same time step.
A test that reads an output in the same region as the edge can therefore see the old value. Scattering arbitrary delays through the test hides the issue without establishing a sound method. Choose stimulus and checking edges deliberately.
Assignments in the testbench
Stimulus signals are usually driven with blocking assignments. Registers in the DUT remain described with nonblocking assignments. This separation reduces races between the test and circuit.
To compare signals that may contain x or z, === and !== make those values visible. With ==, the comparison result can itself become unknown.
Waiting for a useful event
@(posedge i_clk) waits for an edge. wait(o_done) waits until a condition becomes true. repeat (N) repeats an action a known number of times. A delay such as #10 advances by an absolute duration defined by timescale.
Protocol-based waits are often more robust than fixed delays. Waiting for o_valid, for example, survives a latency change better than waiting exactly four periods.
Making the test self-checking
A useful test knows the expected result and fails on its own. It should report at least the scenario, expected value, and received value.
Waveforms remain useful for understanding a failure, but they should not be the only way to know whether one hundred cases passed.
Covering boundaries
For a counter or FIFO, testing only a few normal values is not enough. Also cover reset, wraparound, full, empty, simultaneous requests, and controls held high for several cycles.
Key points
The testbench creates time and stimulus; it is not synthesized.
A nonblocking assignment updates its target later in the same simulation step.
Deliberate stimulus and checking edges prevent races.
=== and !== explicitly detect x and z.
A test should report its own pass or failure result.