5.1 Timing controls in testbenches
Why this matters
A circuit runs all the time, but a testbench is a script, and a script has to decide when to change each input. Timing controls are the statements that make the script wait. Whether a line runs just before a clock edge, at the edge or just after it decides which value the flip-flop sees, so the details matter. Every testbench in the rest of this chapter relies on them.
Learning goals
Section titled “Learning goals”- Use a delay, an edge control and a level control to make a testbench script wait, and say what ends each wait.
- Generate a clock with a
foreverloop inside aninitialblock, and drive a flip-flop from the script. - Explain why sequential stimulus is written with
<=. - Say what value a signal has when the line after a timing control runs.
- Read an
// expect:comment as the value just before the edge that ends its line.
Intuition first
Section titled “Intuition first”A testbench script has three ways to wait. It can set a kitchen timer and do nothing until the time is up. It can wait for the doorbell, and carry on when it rings. Or it can watch the oven light and carry on once the light is on, and if the light is already on, it does not wait at all.
A programmer would write these as sleep(10), as waiting on an event, and as a loop that checks a condition. There is one difference that matters. A testbench is not the only thing running. The clock generator, the script and the circuit under test all run in the same simulation, and simulated time moves forward only when every one of them is waiting for something. So when a script waits, it does not stop the clock or the circuit: it only decides when its own next line runs.
Three ways to wait
Section titled “Three ways to wait”A timing control is a prefix on a statement that suspends the script until something happens, and the statement then runs. There are three kinds. A delay, #10, waits for that many simulated time units. An edge control, @(posedge clk) or @(negedge clk), waits for the next rising or falling edge of a signal. A level control, wait (q == 1), waits until the expression is true, and does not wait at all if it is already true.
Timing controls belong to testbenches. A circuit does not wait for ten time units. It is made of gates and flip-flops that respond to their inputs, and the only waiting it does is for a clock edge, at the top of an always_ff. The book's checks reject a # delay, a wait or an @ that is not at the top of an always_ff in a design file. (The delay models of Section 3.3 are separate files that exist only for simulation.) The Lab 5 handout makes the same point about @(posedge clk): it may sit in the middle of an initial block in a testbench, but in code that is synthesized it belongs only at the top of an always_ff. (fact ch05/lab-posedge)
The two controls that most testbenches use are the delay and the rising-edge control. Their difference is what ends the wait. A delay ends after a fixed time, whatever the clock does. A rising-edge control ends at the next rising edge, whenever that comes. A script written with edge controls follows the clock if the clock period changes, and a script written with delays does not.
When the next line runs
Section titled “When the next line runs”Section 4.8 defined nonblocking assignment (Definition 4.8.2) and gave the rule that a testbench changes its inputs with it. This section shows why the rule works, and to see that you need to know what the simulator does at a single moment of simulated time.
An instant is one value of simulated time, with everything that happens at it. The simulator runs every script and circuit process that is ready at that time. Blocking assignments take effect as they run. When no process is ready any more, the nonblocking assignments take effect, which can make more processes ready, and the simulator repeats this until nothing is left. Only then does simulated time move on. So a nonblocking assignment still takes effect at the same simulated time, but later within the instant than a blocking one.
Now the edge of a clock. At a rising edge several processes become ready at the same instant: the testbench script, which was waiting for the edge, and every flip-flop that is clocked by it. The language does not say which of them runs first, and a testbench must give the same answer for every order.
This is why the rule of Section 4.8 holds. Suppose the script wrote d = 1; right after @(posedge clk). If the script ran first, d would change before the flip-flop read it and the flip-flop would capture the new value; if the flip-flop ran first, it would capture the old one. With d <= 1; the change takes effect only after both have run, so the flip-flop always reads the value d had before the edge. The book's checks reject a blocking assignment to a design input in a sequential testbench.
What <= gives you is a property of the simulator, not of flip-flops. A real flip-flop needs d steady in a short window before and after the edge (Section 4.7). In a simulation the window is a single point, and an input written with <= at an edge counts as changing just after it, so the simulated flip-flop reads the old value. In a circuit the same picture fits an input that another flip-flop on the same clock drives, because that output changes a clock-to-Q delay after the edge, provided the new value does not arrive before the receiving flip-flop's hold time is over. It does not fit an input from outside the circuit, which can change inside the window. This section describes what the simulator does, and so does the rule for // expect: comments below, which says at what moment the book's tools read a signal from the simulation.
The same rule tells you what a line after @(posedge clk) sees. It runs in the same instant as the edge, before the flip-flops' nonblocking updates have taken effect. So a line that reads a flip-flop's output right after the edge reads the value from before the edge.
In SystemVerilog
Section titled “In SystemVerilog”The device under test in this section is a D flip-flop with a synchronous reset: at each rising edge q takes d, or 0 while reset is 1.
// A D flip-flop with a synchronous reset: at each rising clock edge q takes d, or 0 while reset is 1.module dff(input logic clk, reset, d, output logic q); always_ff @(posedge clk) if (reset) q <= 1'b0; else q <= d;endmoduleEvery signal the testbench connects is in the module's port list, reset included, so the .* connection in the testbenches below connects all four ports.
A testbench that reads a flip-flop at the edge shows the rule above with real values. It prints q three times: right after the edge at which q changes, one time unit later, and at the next falling edge.
// A third testbench for dff: print what the testbench itself sees at an edge, a moment later, and at the falling edge.module dff_sample_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk; end
initial begin reset <= 1; d <= 0; @(posedge clk); reset <= 0; d <= 1; @(posedge clk); // This is the edge at which q rises. The next lines run in the same instant. $display("DD q_at_edge=%b", q); #1; $display("DD q_one_unit_later=%b", q); @(negedge clk); $display("DD q_at_falling_edge=%b", q); @(posedge clk); $stop; endendmoduleAt the edge q is 0, the value from before the edge. One time unit later it is 1, and at the falling edge it is 1. The values come from the simulator's own output.
A testbench line right after @(posedge clk) prints the output q of a flip-flop that changes at this very edge, from 0 to 1. What does it print?
Waiting for edges and for delays
Section titled “Waiting for edges and for delays”The testbench below generates a clock, resets the flip-flop, and then drives d in two ways: at clock edges, and a quarter of a period after them.
// Testbench for dff: inputs change at clock edges, then a quarter of a period after an edge.module dff_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk; end
initial begin reset <= 1; d <= 0; @(posedge clk); reset <= 0; d <= 1; @(posedge clk); // expect: q=0 d <= 0; @(posedge clk); // expect: q=1 // d is 0 now. A quarter of a period after that edge it goes high, and it is low again before the next edge. #(CLOCK_PERIOD / 4); // expect: d=0, q=0 d <= 1; #(CLOCK_PERIOD / 2); // expect: d=1, q=0 d <= 0; @(posedge clk); // expect: d=0, q=0 // A quarter of a period after the next edge d goes high and stays high. #(CLOCK_PERIOD / 4); // expect: d=0, q=0 d <= 1; @(posedge clk); // expect: d=1, q=0 @(posedge clk); // expect: q=1 $stop; endendmoduleThe clock comes from the first initial block. It sets clk to 0 and then repeats #(CLOCK_PERIOD / 2) clk <= ~clk; for the rest of the simulation. A forever loop is a statement, so it has to sit inside a procedural block such as initial. Section 4.8 shows other ways to write the same clock.
In the second initial block, each line of the form d <= 0; @(posedge clk); sets inputs and then waits for the next rising edge. The lines with #(CLOCK_PERIOD / 4) and #(CLOCK_PERIOD / 2) wait for a quarter and for a half of a period, counted from the moment the script reaches them.
Time in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-edges: time in ps from 0 to 350, 14 columns of 25 ps.
clk: clock, period 100 ps, starts 0, 3 rising edges in the window (50, 150, 250).
reset: 1 at 0, then 0 at 50.
d: 0 at 0, then 1 at 50, 0 at 150, 1 at 275, 0 at 325.
q: unknown at 0, then 0 at 50, 1 at 150, 0 at 250.
Dashed vertical guides mark the rising clock edges.
Description: A timing diagram of clk, reset, d and q. reset is high until the first rising edge. d rises at the first rising edge and falls at the second. q is unknown until the first rising edge, then low, high from the second rising edge, and low again from the third. A quarter of a period after the third rising edge d goes high for half a clock period and then low again, and q stays low.
Read Figure 5.1.1 edge by edge. At the first rising edge reset is still 1, so q becomes 0, and q was unknown before. The script resumes at that edge, and its next line, reset <= 0; d <= 1;, takes effect right after the edge: reset falls and d rises at the same instant as the edge, which the simulator counts as just after it. At the second rising edge the flip-flop reads the 1 that d had before the edge, and q becomes 1. The script's line d <= 0; also takes effect at that instant, which is why d falls at the same moment q rises. At the third rising edge d is 0, so q falls.
Then the script waits for a quarter of a period, lets d rise, waits for half a period, and lets d fall again. This pulse on d starts and ends between two rising edges, so no edge sees it, and q does not move. The second figure continues the run.
Time in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-held: time in ps from 325 to 575, 10 columns of 25 ps.
clk: clock, period 100 ps, starts 0, 2 rising edges in the window (350, 450).
reset: 0 at 325, no changes.
d: 0 at 325, then 1 at 375.
q: 0 at 325, then 1 at 450.
Dashed vertical guides mark the rising clock edges.
Description: A timing diagram of clk, reset, d and q. d is low at the left of the figure, and clk rises shortly after. A quarter of a period after that rising edge d goes high. q stays low until the next rising edge of clk, where it rises. d and q stay high until the end of the figure.
In Figure 5.1.2 d rises a quarter of a period after an edge and stays high. q does not follow it. It rises only at the next rising edge, three quarters of a period after d rose. The flip-flop looks at its input at the edge and at no other moment.
Now the // expect: comments. A comment on a line checks the named signals one time unit before the timing control that ends the line completes. For d <= 0; @(posedge clk); // expect: q=1 that is one time unit before the third rising edge, when q still holds what the previous edge gave it, which is 1. For a line that ends with a delay, such as d <= 1; #(CLOCK_PERIOD / 2); // expect: d=1, q=0, it is one time unit before that delay ends, in the middle of the cycle. In the middle of a cycle d is already 1 and q is still 0.
Waiting for a level
Section titled “Waiting for a level”A second testbench for the same flip-flop waits for q to become 1 and not for a clock edge.
// A second testbench for dff: the stimulus waits for a level with wait() instead of for a clock edge.module dff_wait_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk; end
initial begin reset <= 1; d <= 0; @(posedge clk); // d goes high now. wait() holds this process until q is 1, which happens at the next edge. reset <= 0; d <= 1; wait (q == 1); // This line runs in the same instant as that edge, right after q changed. d <= 0; @(posedge clk); // expect: q=1 @(posedge clk); // expect: q=0 $stop; endendmoduleThe script reaches the wait (q == 1) right after the first edge, when q is not 1, so it waits. The wait ends when q changes to 1, which happens at the second rising edge, when the flip-flop reads the 1 that d has had since the first edge. The script resumes in that same instant, right after the change. So the next line, d <= 0;, takes effect at the second rising edge.
Time in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-wait: time in ps from 0 to 350, 14 columns of 25 ps.
clk: clock, period 100 ps, starts 0, 3 rising edges in the window (50, 150, 250).
reset: 1 at 0, then 0 at 50.
d: 0 at 0, then 1 at 50, 0 at 150.
q: unknown at 0, then 0 at 50, 1 at 150, 0 at 250.
Dashed vertical guides mark the rising clock edges.
Description: A timing diagram of clk, reset, d and q. d rises at the first rising edge and falls at the second. q is unknown until the first rising edge, low, high from the second rising edge and low from the third.
In Figure 5.1.3 d falls at the second rising edge, at the same moment as q rises, although the script was waiting for q and not for the clock. The flip-flop had read d before the change, so it is not affected. Then @(posedge clk) brings the script to the third edge, where q falls because d is 0 now.
A wait is the one of the three controls that does not wait at all when its condition already holds: the script goes straight on. Exercise 5.1.3 shows that. An edge control always waits for a new edge.
Course conventions
Section titled “Course conventions”How CSE 369 writes this
In CSE 369 we generate a testbench clock with a forever loop inside an initial block, write sequential stimulus with <=, define every input at time zero, and end with an extra clock edge after the last input change. A @(posedge clk) in the middle of an initial block is fine in a testbench and never in a design.
The time axis of a simulated waveform counts the simulator's time units, and the book's figures label it ps. Those numbers are arbitrary: the course's testbenches use a clock period of 100 time units (fact ch05/clock-period), and a label of ps does not describe a real clock that fast, or any real hardware speed.
Pitfalls
Section titled “Pitfalls”Common pitfall
- Putting a delay or a
waitin a design. They exist only in the simulator; a design describes hardware. - Assuming the script runs after the flip-flops at an edge. The language does not order them, so only the nonblocking
<=in the script makes the result the same whichever runs first. - Reading an
// expect:comment as the result of the edge that ends its line. It reports the value just before that edge, so it is the result of the edge before. - Writing
foreverdirectly in the module. It is a statement, so it must be inside aninitialblock. - Expecting
wait (q == 1)to pause whenqis already 1. It does not. - Reading a waveform where an input changes exactly at an edge as if the flip-flop saw the new value. In the simulation it sees the value from before the edge. In hardware an input that changes at the edge breaks the flip-flop's hold requirement of Section 4.7 whenever the hold time is greater than zero, and then the result is not defined.
Check your understanding
Section titled “Check your understanding”Take the quiz (5 questions)
A testbench line right after @(posedge clk) reads a flip-flop output that changes at this edge from 0 to 1. One time unit later it reads the output again. What does the second read give?
The script reads the same output again at the next falling edge, half a period after the rising edge. What does it read?
q since the rising edge, and the new value is in place.At a rising edge the testbench script and a flip-flop are both ready in the same instant, and the script runs d <= 1;. When does d change, compared with the moment the flip-flop reads its input?
In a testbench, a comment // expect: q=1 is on the line d <= 0; @(posedge clk);. When is q read?
q yet.Which of these describes hardware, and so may appear in a design file?
always_ff describes a flip-flop. A delay or a wait describes the behavior of the simulator, which hardware does not have. A #10 in a design file is legal SystemVerilog, but it says nothing about hardware, which is why the book keeps delays in separate simulation-only models and its checks reject them in designs.Exercises
Section titled “Exercises”The figure shows the clock and the inputs of the flip-flop for a testbench in which inputs change a quarter of a period after the clock edges. Draw q. The figure starts at a rising clock edge. An input that changes between two edges is seen by the next edge.
Time in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-trace-q: time in ps from 50 to 400, 14 columns of 25 ps.
clk: clock, period 100 ps, starts 1, 3 rising edges in the window (150, 250, 350).
reset: 1 at 50, then 0 at 75, 1 at 275.
d: 1 at 50, then 0 at 175, 1 at 225.
Empty lanes for the reader to complete: q.
Dashed vertical guides mark the rising clock edges.
Description: A timing diagram of clk, reset and d, with a blank lane for q. reset is high at the start, and falls a quarter of a period after the first rising edge. d is high at the start, falls a quarter of a period after the second rising edge, rises half a period later, and stays high. reset rises again a quarter of a period after the third rising edge.
Hint
reset and d just before each rising edge, in that order: reset has priority.Solution
At each rising edge, q becomes 0 if reset was high just before it, and otherwise the value d had just before it.
- First edge:
resetis high, soqis 0, and the 1 ondis ignored. - Second edge:
resetfell a quarter of a period after the first edge, so it is low.dis 1, soqbecomes 1. - Third edge:
dfell after the second edge and rose again before the third, so it is 1 at the edge.qstays 1, and the dip ofdis never seen. - Fourth edge:
resetrose a quarter of a period after the third edge, so it is high.qbecomes 0, whateverdis.
Time in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-trace-q: time in ps from 50 to 400, 14 columns of 25 ps.
clk: clock, period 100 ps, starts 1, 3 rising edges in the window (150, 250, 350).
reset: 1 at 50, then 0 at 75, 1 at 275.
d: 1 at 50, then 0 at 175, 1 at 225.
q: 0 at 50, then 1 at 150, 0 at 350.
Dashed vertical guides mark the rising clock edges.
Description: The full timing diagram of clk, reset, d and q. q is low until the second rising edge, high from the second rising edge to the fourth, and low from the fourth.
This is the stimulus that produced the figure. Each // expect: comment is the value just before the edge or the delay that ends its line.
// Exercise: the stimulus for a trace question. Inputs change a quarter of a period after edges, never at an edge.module dff_trace_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk; end
initial begin reset <= 1; d <= 1; @(posedge clk); #(CLOCK_PERIOD / 4); // expect: q=0 reset <= 0; @(posedge clk); // expect: q=0 #(CLOCK_PERIOD / 4); // expect: q=1 d <= 0; #(CLOCK_PERIOD / 2); // expect: q=1 d <= 1; @(posedge clk); // expect: q=1 #(CLOCK_PERIOD / 4); // expect: q=1 reset <= 1; @(posedge clk); // expect: q=1 @(posedge clk); // expect: q=0 $stop; endendmoduleThis testbench does not compile. What is wrong, and how do you fix it?
// Exercise: a testbench for dff that does not compile.module dff_badclk_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk;
initial begin reset <= 1; d <= 0; @(posedge clk); reset <= 0; d <= 1; @(posedge clk); @(posedge clk); $stop; endendmoduleHint
Solution
The compiler says so itself:
hdl/ch05/s01/ex/dff_badclk_tb.sv:3:24: warning: variable 'q' is assigned but its value is never used [-Wunused-but-set-variable]
logic clk, reset, d, q;
^
hdl/ch05/s01/ex/dff_badclk_tb.sv:7:13: warning: unused parameter 'CLOCK_PERIOD' [-Wunused-parameter]
parameter CLOCK_PERIOD = 100;
^~~~~~~~~~~~
hdl/ch05/s01/ex/dff_badclk_tb.sv:10:3: error: expected member
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
^~~~~~~
hdl/ch05/s01/ex/dff_badclk_tb.sv:10:35: error: expected a declaration name
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
^
hdl/ch05/s01/ex/dff_badclk_tb.sv:10:42: error: expected a declaration name
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
^The first error is on the line with forever. The two warnings above it do not point at the mistake: CLOCK_PERIOD looks unused because the only line that read it failed to parse, and q is reported because this testbench never reads it. A module may directly contain declarations, instances and procedural blocks such as initial, and a forever loop is a statement, which can only live inside a block. The fix is to put the loop in an initial block, together with the line that sets clk to 0 first, as the clock generator in Example 5.1.1 does.
This testbench waits twice in a row, then waits for a clock edge, and prints the simulated time after each wait. The clock rises at 50, 150, 250 and so on. Predict the three times.
// Exercise: two wait() statements in a row, each followed by a print of the simulated time.module dff_wait_twice_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk; end
initial begin reset <= 1; d <= 0; @(posedge clk); reset <= 0; d <= 1; wait (q == 1); $display("DD t_first=%0t", $time); wait (q == 1); $display("DD t_second=%0t", $time); @(posedge clk); $display("DD t_next_edge=%0t", $time); d <= 0; @(posedge clk); $stop; endendmoduleHint
q become 1? What does a wait do when its expression is already true?Solution
The first wait ends when q rises, at the second rising edge, which is at time 150. At that moment q is already 1, so the second wait ends at once, and the time it prints is the same, 150. The edge control that follows does wait: it ends at the next rising edge, at time 250.
Write the stimulus for the flip-flop testbench that produces this waveform: q is high from the second rising edge to the fourth. Change inputs only a quarter of a period after an edge, or at an edge, use <=, and end with one extra edge after the edge that shows the last change.
Time in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-target: time in ps from 50 to 400, 14 columns of 25 ps.
clk: clock, period 100 ps, starts 1, 3 rising edges in the window (150, 250, 350).
reset: 1 at 50, then 0 at 75.
q: 0 at 50, then 1 at 150, 0 at 350.
Dashed vertical guides mark the rising clock edges.
Description: A timing diagram of clk, reset and q. reset is high until a quarter of a period after the first rising edge. q is low until the second rising edge, high from the second rising edge to the fourth, and low after that.
Hint
q has to be 1 after the second edge, so what is d just before it? And before the fourth edge, d has to be back to 0. When can it change?Solution
q takes d at each edge, so d must be 1 before the second and third edges and 0 before the fourth. The easiest place to raise d is the instant the reset falls, a quarter of a period after the first edge, and to lower it is a quarter of a period after the third. Here is the stimulus, with the value of q just before the end of each line.
// Exercise solution: a stimulus that makes q high from the second rising edge to the fourth.// Inputs change a quarter of a period after an edge, and the test ends one edge after the last change.module dff_target_tb(); logic clk, reset, d, q;
dff dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin clk <= 0; forever #(CLOCK_PERIOD / 2) clk <= ~clk; end
initial begin reset <= 1; d <= 0; @(posedge clk); #(CLOCK_PERIOD / 4); // expect: q=0 reset <= 0; d <= 1; @(posedge clk); // expect: q=0 @(posedge clk); // expect: q=1 #(CLOCK_PERIOD / 4); // expect: q=1 d <= 0; @(posedge clk); // expect: q=1 @(posedge clk); // expect: q=0 $stop; endendmoduleTime in ps; one column is 25 ps.
Timing diagram from simulation, ch05/dff-target: time in ps from 50 to 400, 14 columns of 25 ps.
clk: clock, period 100 ps, starts 1, 3 rising edges in the window (150, 250, 350).
reset: 1 at 50, then 0 at 75.
d: 0 at 50, then 1 at 75, 0 at 275.
q: 0 at 50, then 1 at 150, 0 at 350.
Dashed vertical guides mark the rising clock edges.
Description: The timing diagram of clk, reset, d and q. d rises a quarter of a period after the first rising edge and falls a quarter of a period after the third.