Lesson 11: Testbench
The function of a testbench is to apply stimulus (inputs) to the Design Under Test (DUT), sometimes called the Unit Under Test (UUT), and report the outputs in a readable and user-friendly format. In this section, we discuss how an efficient testbench can be written. Procedure steps requiring you to write the testbench for the design directly refer to elements of the testbench discussed in this section. Note: Many of the coding techniques used in testbenches (such as file I/O, the initial block, etc) are not suitable for synthesis.
Time Scale And Time Precision
Time Scale And Time Precision
Verilog simulation depends on how time is defined because the simulator needs to know what a means in terms of time. The `timescale compiler directive specifies the time unit and precision for the modules that follow it.
Syntax | Description |
---|---|
`timescale <time_unit><base>/<time_precision><base> |
|
$printtimescale | System task to display time unit and precision |
$time and $realtime | System functions return the current time and the default reporting format that can be changed with $timeformat. |
For example:
`timescale 1ns/100ps
`timescale 10us/100ns
`timescale 10ns/1ns
Let us design a testbench with different time and precision units, and use delay statement #n to make the simulator wait for an n time unit.
- #1: delay 1-time unit
- #0.49: less than half a time unit delay
- #0.5: delay a half time unit
- #0.51: delay more than half the time unit
- #5: delay 5-time unit
Example #1: 1ns/1ns
// declare the timescale where time_uint is 1 ns // and time_precision is also 1 ns `timescale 1ns/1ns module test; reg val; initial begin // initial val to 0 at time 0 uints val = 0; #1 $display ("T = %0t at time #1", $realtime); val = 1; #0.49 $display ("T = %0t at time #0.49", $realtime); val = 0; #0.50 $display ("T = %0t at time #0.50", $realtime); val = 1; #0.51 $display ("T = %0t at time #0.51", $realtime); val = 0; #5 $display ("T = %0t end of simulation", $realtime); end endmodule
The simulation log and waveform as below:
VSIM54 > run -all
# T = 1 at time #1
# T = 1 at time #0.49
# T = 2 at time #0.50
# T = 3 at time #0.51
# T = 8 end of simulation
The first delay statement is #1, which makes the simulator wait for a 1-time unit that is specified to be 1 ns with the `timescale directive. The second delay statement used #0.49, which is less than half a time uint. However, the time precision is specified to be 1 ns, and hence the simulator cannot go smaller than 1 ns which makes it ti round the given delay statement and yields 0ns. So, the second delay fails to advance the simulation time, but the val value still changed.
The third delay statement uses exactly half the time unit #0.5 and again that the simulator will round the dealy value to 1, which represents one whole time unit. So, this gets printed at T = 2.
The fourth delay statement uses a value more than half the time unit and gets rounded as well making the display statement to be printed at T = 3.
The simulator runs for 8 ns, but notice that the waveform does not have smaller divisions between each time unit (ns). This is because the precision of time is the same as the time unit.
Example #2: 10ns/1ns
// declare the timescale where time_uint is 1 ns // and time_precision is also 1 ns `timescale 10ns/1ns module test; reg val; initial begin // initial val to 0 at time 0 uints val = 0; #1 $display ("T = %0t at time #1", $realtime); val = 1; #0.49 $display ("T = %0t at time #0.49", $realtime); val = 0; #0.50 $display ("T = %0t at time #0.50", $realtime); val = 1; #0.51 $display ("T = %0t at time #0.51", $realtime); val = 0; #5 $display ("T = %0t end of simulation", $realtime); end endmodule
VSIM54 > run -all
# T = 10 at time #1
# T = 15 at time #0.49
# T = 20 at time #0.50
# T = 25 at time #0.51
# T = 75 end of simulation
Example #3: 1ns/1ps
// declare the timescale where time_uint is 1 ns // and time_precision is also 1 ns `timescale 1ns/1ps module test; reg val; initial begin // initial val to 0 at time 0 uints val = 0; #1 $display ("T = %0t at time #1", $realtime); val = 1; #0.49 $display ("T = %0t at time #0.49", $realtime); val = 0; #0.50 $display ("T = %0t at time #0.50", $realtime); val = 1; #0.51 $display ("T = %0t at time #0.51", $realtime); val = 0; #5 $display ("T = %0t end of simulation", $realtime); end endmodule
VSIM54 > run -all
# T = 1000 at time #1
# T = 1490 at time #0.49
# T = 1990 at time #0.50
# T = 2500 at time #0.51
# T = 7500 end of simulation
Free Running Clock Signal
Free Running Clock Signal
All the sequential circuits require a clock signal. To generate a clock signal, many different Verilog constructs can be used. The following examples show you how to generate a clock signal in testbench Verilog.
Clock Generation
Clock Generation
A way to generate a free-running clock signal is by using a forever statement that creates an infinite loop. A forever loop initiates the continuous execution of one or more procedural statements during the simulation.
The following are some of the methods for clock generation. More or less, they all are the same.
`timescale 1ns/100ps module test(); reg clk; parameter hperi = 5; parameter timeout = 50; initial begin clk = 1'b0; end always begin forever #hperi ctl = ~clk; end initial begin // Your Code for Test Plan #timeout $stop; end endmodule
`timescale 1ns/100ps module test(); reg clk; parameter hperi = 5; parameter timeout = 50; initial begin clk = 1'b0; forever #hperi ctl = ~clk; end initial begin // Your Code for Test Plan #timeout $stop; end endmodule
We can replace forever with an always block to create an infinite loop.
`timescale 1ns/100ps module test(); reg clk; parameter hperi = 5; parameter timeout = 50; initial clk = 1'b0; always #hperi ctl = ~clk; initial begin // Your Code for Test Plan #timeout $stop; end endmodule
`timescale 1ns/100ps module test(); reg clk; parameter hperi = 5; parameter timeout = 50; always #hperi ctl = ~clk; initial begin clk = 1'b0; // Your Code for Test Plan #timeout $stop; end endmodule
Clock with Duty Cycle Setting
Clock with Duty Cycle Setting
The following example shows the clock generation with a parameterizable duty cycle. By changing the DUTY_CYCLE parameter, different clocks can be generated. It is beneficial to use parameters to represent the delays, instead of hard coding them. In a single testbench, if more than one clock is needed with different duty cycles, passing duty cycle values to the instances of clock generators is easy than hard coding them.
`timescale 1ns/1ns module test(); parameter CLK_PERIOD = 10; parameter DUTY_CYCLE = 70; //70% duty cycle parameter TCLK_HI = (CLK_PERIOD * DUTY_CYCLE / 100.0); parameter TCLK_LO = (CLK_PERIOD - TCLK_HI); parameter timeout = 50; reg clk; initial clk = 0; always begin #TCLK_LO clk = 1'b1; #TCLK_HI clk = 1'b0; end initial begin // Your Code for Test Plan #timeout $stop; end endmodule
Make sure that parameter values are properly dividable.
Jittered Clock
Jittered Clock
Often clock generators are required to generate clock with jitter. The following is a simple way to generate a clock with jitter.
`timescale 1ns/1ns module test(); reg clk; wire jittered_clk; integer jitter; parameter range = 5; parameter timeout = 50; initial clk = 1'b0; always #hperi ctl = ~clk; jitter = $random() % range; assign jittered_clk = #(jitter) clk; initial begin // Your Code for Test Plan #timeout $stop; end endmodule
With the above approach, the overall clock period is increased. A better approach for clock divider is as follows.
parameter DELAY = TIMEPERIOD/2.0 - range/2.0; initial clk = 1'b0; always begin jitter = $dist_uniform(seed, 0, jitter_range); #(DELAY + jitter) clk = ~clk; end
Clock Dividers and Multipliers
Clock Dividers and Multipliers
Clock dividers and multipliers are needed when more than one clock is needed to be generated from the base clock and it should be deterministic. Clock multipliers are simple to design. A simple counter does this job. The clock division is a little bit tricky. TO design a lock divider i.e a frequency multiplier, first the time period has to be captured and then it is used to generate another clock. With the following approach, the jitter in the base clock is carried to a derived clock.
// EXAMPLE:Clock multipler with N times multiplication initial i = 0; always @( base_clock ) begin i = i % N; if (i == 0) derived_clock = ~derived_clock; i = i + 1; end
// EXAMPLE:Clock division with N times division initial begin derived_clock = 1'b0; period = 10; // for initial clock forever derived_clock = #(period/(2*N)) ~derived_clock; end always@(posedge base_clock) begin T2 <= $realtime; period <= T2 - T1; T1 <= T2; end
NOTE: Simulation with `timescale 1ns/1ns is faster than `timescale 1ns/10ps. A simulation using a `timescale 10ns/10ns and with `timescale 1ns/1ns will take the same time.
Timing Control and Applying Stimulus
Timing Control
The Verilog language provides two types of explicit timing control over when simulation time procedural statements are to occur.
- The first type is a delay control in which an expression specifies the time duration between initially encountering the statement and when the statement actually executes.
- The second type of timing control is the event expression, which allows statement execution.
- The third subsection describes the wait statement which waits for a specific variable to change.
Verilog is a discrete event time simulator, i. e., events are scheduled for discrete times and placed on an ordered-by-time wait queue. The earliest events are at the front of the wait queue and the later events are behind them. The simulator removes all the events for the current simulation time and processes them. During the processing, more events may be created and placed in the proper place in the queue for later processing. When all the events of the current time have been processed, the simulator advances time and processes the next events at the front of the queue.
If there is no timing control, simulation time does not advance. Simulated time can only progress by one of the following:
- gate or wire delay, if specified.
- a delay control, introduced by the # symbol.
- an event control, introduced by the @ symbol.
- the wait statement.
The order of execution of events at the same clock time may not be predictable.
Delay Control (#)
Delay Control (#)
A delay control expression specifies the time duration between initially encountering the statement and when the statement actually executes. For example:
#10 A = A + 1;
specifies to delay 10-time units before executing the procedural assignment statement. The # may be followed by an expression with variables.
Events (@)
Events (@)
The execution of a procedural statement can be triggered with a value change on a wire or register or the occurrence of a
named event. Some examples:
@r begin // controlled by any value change in the register r
A = B & C;
end
@(posedge clk2) A = B & C; // controlled by positive edge of clk2
@(negedge clk3) A = B & C; // controlled by negative edge of clk3
forever @(negedge clk) // controlled by negative edge of clk
begin
A = B & C;
end
In the forms using posedge and negedge, they must be followed by a 1-bit expression, typically a clock. A negedge is detected on the transition from 1 to 0 (or unknown), which also called falling edge or negative edge. A posedge is detected on the transition from 0 to 1 (or unknown), which class rising edge or positive edge.
wait Statements
wait Statements
The wait statement allows a procedural statement or a block to be delayed until a condition becomes true.
wait (A == 3)
begin
A = B & C;
end
The difference between the behavior of a wait statement and an event is that the wait statement is level sensitive whereas @(posedge clock); is triggered by a signal transition or is edge sensitive.
Event Trigger
Verilog also provides features to name an event and then trigger the occurrence of that event. We must first declare the event:
event event6;
To trigger the event, we use the -> symbol :
-> event6;
To control a block of code, we use the @ symbol as shown:
@(event6) begin
<some procedural code>
end
We assume that the event occurs in one thread of control, i. e., concurrently, and the controlled code is in another thread. Several events may to or-ed inside the parentheses.
File Access and Loop
Pre-Simulation
Post-Simulation