3.1 Testbenches
Why this matters
A software bug can be found with a print statement. A circuit has no print statement: once it is built, you cannot ask a wire what it is carrying. So hardware designers test the design in a simulator first, where every wire can be watched, and fix the bug while it is still cheap. A testbench is the program that exercises the design in the simulator, and every module in this book comes with one.
Learning goals
Section titled “Learning goals”- Explain what a simulator and a testbench are, and why a testbench has no ports.
- Write a testbench for a combinational module that sets every input at time zero and steps through input combinations with delays.
- Test every input combination of a small module with a
forloop. - Predict a module's output on each line of a testbench, including an output that is unknown (X).
- Read a waveform of a testbench run and match each change to the line that caused it.
Intuition first
Section titled “Intuition first”Think of a unit test in a programming language. You call a function with some arguments and compare what comes back with what you expected. A testbench is the same idea for a circuit, with one difference that matters: a circuit is not called. It is always running, and its outputs are always a function of its inputs right now. So the testbench does not call the module. It plays the part of the rest of the world: it holds the module's inputs at some values for a while, changes them, holds them again, and the simulator records what the outputs did.
A good picture is a test rig in a workshop. The device sits on the bench with its input pins wired to a signal generator and its output pins wired to a probe. The rig contains the device, the generator and the probe, and nothing is wired out of the rig, so the rig itself needs no connectors. That is why a testbench module has an empty port list. The probe is the waveform you look at afterwards.
What a simulator does
Section titled “What a simulator does”A simulator is a program that reads a hardware description and computes, for each moment of simulated time, the value on every signal in the design. It runs on your own computer. It is not the circuit, and it never touches an FPGA; it is a model of what the circuit would do.
Lecture 3 names ModelSim and Icarus Verilog as the simulators. (fact ch03/simulators) The book's own checks run Icarus; the section slides show a ModelSim workflow and a workflow that writes a waveform file and opens it in the Surfer viewer. (fact ch03/viewer-workflows) The waveforms in this book are drawn from Icarus runs by the book's own tools, so what you read on a page is what a simulator produced for the listing above it.
Simulation time is separate from your wall clock. The simulator keeps a counter of simulated time, and a delay such as #10 in a testbench tells it to run the design forward by ten time units before executing the next line. Inside one simulated instant the simulator can update several signals and run several processes, one after another, while zero-delay logic settles. A waveform is drawn against time, so it gives those intermediate updates no width: a combination of inputs shows up only if time moves before the next one is applied. Put a delay between input combinations when you want each one to occupy a visible interval.
A testbench is a module that contains an instance of the circuit being tested, called the device under test or DUT, and a small script that supplies values to every input of the DUT over simulated time. The testbench itself is never turned into hardware: it exists only in the simulator.
A testbench has the same four parts every time.
- A module named after the module under test, with
_tbadded, and no ports. - A signal for every port of the DUT. Inputs are the signals the script drives, and outputs are the signals you watch.
- An instance of the DUT named
dut. When the signal names match the port names,.*connects every port at once. Naming each port, as in.s(s), or naming a port once when the signal has the same name, as in.s, does the same job. - An
initialblock with the script. It assigns every input before the first delay, then alternates between changing inputs and waiting, and ends with$stop. The section slides say the$stopis what makes ModelSim pause at the end. (fact ch03/modelsim-stop)
Every module you write gets a testbench, in its own file, before it goes anywhere near a board.
In SystemVerilog
Section titled “In SystemVerilog”A first testbench
Section titled “A first testbench”A multiplexer, or mux, picks one of its data inputs and passes it to the output. This one, mux2, has the ports of the multiplexer in Definition 2.7.2: it passes d1 when s is 1 and d0 when s is 0.
// A 2-to-1 multiplexer: y follows d1 when s is 1 and follows d0 when s is 0.module mux2 ( input logic s, input logic d0, input logic d1, output logic y); assign y = s ? d1 : d0;endmoduleHere is a testbench with four steps. Each step sets some inputs and waits ten time units.
// Testbench for mux2: four combinations of the inputs, ten time units apart.module mux2_tb(); logic s, d0, d1, y;
mux2 dut(.*);
initial begin s = 1; d1 = 0; d0 = 1; #10; // expect: y=0 d1 = 1; #10; // expect: y=1 s = 0; #10; // expect: y=1 d0 = 0; #10; // expect: y=0 $stop; endendmoduleWalk through it one line at a time, using what the mux does.
- The first line sets all three inputs,
s,d1andd0. Becausesis 1,yfollowsd1, which is 0, soyis 0. - The second line changes only
d1, to 1. Withsstill 1,yfollowsd1and becomes 1. - The third line sets
sto 0, soynow followsd0, which is still 1.ystays 1. - The fourth line changes
d0to 0.yfollows it and becomes 0.
The comment on each line starts with expect:. These comments are the book's own device: the book's tools run the testbench and compare each comment with the simulated value of the named signal. A comment is read one time unit before the delay on its line finishes, which is after the inputs of that line have been applied and the logic has settled. So for this circuit a comment names the output that the inputs on its own line produce. Nothing in a simulator reads these comments. In a testbench of your own they are notes to yourself, and the way to make the simulator do the comparing is a self-checking testbench like the one in the aside below. Section 4.8 shows testbenches with a clock, where the same comment reports the result of the previous clock edge instead.
Time in ps; one column is 10 ps.
Timing diagram from simulation, ch03/mux2-trace: time in ps from 0 to 40, 4 columns of 10 ps.
s: 1 at 0, then 0 at 20.
d0: 1 at 0, then 0 at 30.
d1: 0 at 0, then 1 at 10.
y: 0 at 0, then 1 at 10, 0 at 30.
Description: A timing diagram of s, d0, d1 and y over four steps. y is low in the first step, high in the second and third, and low in the fourth.
Compare Figure 3.1.1 with the four lines. In the first step s is high, d1 is low, d0 is high and y is low. In the second step d1 rises, and y rises at the same instant. At the start of the third step s falls, and y stays high because d0 is high. In the fourth step d0 falls, and y falls with it. Each edge of y lines up exactly with the edge of the input that caused it.
That last observation is a property of this kind of simulation. By default a simulated gate takes no time at all, so a change at an input reaches the output in the same instant. The section slides say that the simulations in the course give every gate a delay of zero. (fact ch03/zero-delay-setup) A real gate takes a little time, and Section 3.3 adds that delay to the model. Until then, any delay you see in a waveform is a # delay in the testbench.
In the testbench above, suppose the second line were d1 = 1; s = 0; #10;, changing both inputs at once. With d0 still 1, what is y after that line?
s equal to 0 the mux passes d0, whatever d1 is, and d0 is 1. The two assignments take effect at the same simulated time. The simulator may apply them one after the other inside that instant, but the waveform has no width in which to show a step between them, and the value that remains is the one the mux function gives for the final inputs.Trying every input
Section titled “Trying every input”For a combinational module the output depends on the current inputs only, and nothing else. If the module has few inputs, you can test it completely: apply every combination once. A module with input bits has combinations, so four input bits need sixteen steps. Writing sixteen lines by hand is tedious and error-prone. A for loop counts through the combinations for you.
A seven-segment display shows a digit by lighting some of its seven segments. The decoder below takes a 4-bit code and produces seven bits, one per segment, where 1 means lit. Segment L0 is the top bar and the others follow the lecture's numbering, so leds[0] drives the top. Only the codes 0 to 9 are digits. For the other six the module sets the output to 7'bX, all unknown, to say that the output does not matter there. Section 3.9 returns to this decoder.
// Seven-segment decoder for the digits 0 to 9. leds[i] drives segment Li and 1 means lit.// The codes 10 to 15 are not digits, so their output is left unknown.module seg7_digit ( input logic [3:0] bcd, output logic [6:0] leds); always_comb begin case (bcd) // leftmost bit is segment 6 4'd0: leds = 7'b0111111; 4'd1: leds = 7'b0000110; 4'd2: leds = 7'b1011011; 4'd3: leds = 7'b1001111; 4'd4: leds = 7'b1100110; 4'd5: leds = 7'b1101101; 4'd6: leds = 7'b1111101; 4'd7: leds = 7'b0000111; 4'd8: leds = 7'b1111111; 4'd9: leds = 7'b1101111; default: leds = 7'bX; endcase endendmoduleThe testbench loops over all sixteen values of the input, twenty time units each.
// Testbench for seg7_digit: each of the sixteen input codes in turn, twenty time units apiece.module seg7_digit_tb(); logic [3:0] bcd; logic [6:0] leds;
seg7_digit dut(.*);
initial begin for (int code = 0; code < 16; code++) begin bcd = code[3:0]; #20; end $stop; endendmoduleThe loop counter code is an int and bcd is four bits wide, so the testbench assigns code[3:0], the low four bits. The for loop is not hardware. It does not describe sixteen copies of anything. It is a shorthand for sixteen lines of stimulus, run once, in order, by the simulator.
The run is 320 time units long, so it is drawn in three pieces, in Figure 3.1.2, Figure 3.1.3 and Figure 3.1.4. The bcd lane shows the input in hexadecimal and the leds lane shows the seven output bits in binary, bit 6 on the left.
Time in ps; one column is 20 ps.
Timing diagram from simulation, ch03/seg7-digits-a: time in ps from 0 to 120, 6 columns of 20 ps.
bcd (4 bits, hex): 0 at 0, then 1 at 20, 2 at 40, 3 at 60, 4 at 80, 5 at 100.
leds (7 bits, bin): 0111111 at 0, then 0000110 at 20, 1011011 at 40, 1001111 at 60, 1100110 at 80, 1101101 at 100.
Description: A timing diagram of bcd in hexadecimal and leds in binary. bcd counts from 0 to 5, one value per step, and leds shows a different seven-bit pattern for each.
Time in ps; one column is 20 ps.
Timing diagram from simulation, ch03/seg7-digits-b: time in ps from 120 to 200, 4 columns of 20 ps.
bcd (4 bits, hex): 6 at 120, then 7 at 140, 8 at 160, 9 at 180.
leds (7 bits, bin): 1111101 at 120, then 0000111 at 140, 1111111 at 160, 1101111 at 180.
Description: A timing diagram of bcd in hexadecimal and leds in binary. bcd counts from 6 to 9, and leds shows a different seven-bit pattern for each.
Time in ps; one column is 20 ps.
Timing diagram from simulation, ch03/seg7-digits-c: time in ps from 200 to 320, 6 columns of 20 ps.
bcd (4 bits, hex): A at 200, then B at 220, C at 240, D at 260, E at 280, F at 300.
leds (7 bits, bin): unknown at 200, no changes.
Description: A timing diagram of bcd in hexadecimal and leds in binary. bcd counts from A to F. leds is unknown, drawn hatched, for all six codes.
In the first two figures each code gives a different pattern. In the last figure, from code A to code F, the leds lane is hatched: every bit is unknown. That is the default branch of the module at work. The hatching is a message to the reader, not a signal level: the simulator is saying it has no value to report for those inputs.
A loop over every input is practical only while the input is small. Twenty input bits would mean about a million steps, and a 32-bit adder's two operands together have 64 bits, which is far too many to try. Those are tested with chosen cases instead. Combinational modules with a handful of inputs, which is nearly everything in this chapter, are tested exhaustively.
Unknown inputs and the default branch
Section titled “Unknown inputs and the default branch”Here is a module with a different shape, a case that picks one of four bit patterns. A 2-bit input gait says how fast the horse moves, from standing at 0 up to cantering at 3, and the module drives a row of eight lights called lights: the faster the gait, the more of them are on.
// The faster the gait, the more of the eight lights are on, counting from the right.module horse_gait ( input logic [1:0] gait, output logic [7:0] lights); always_comb begin case (gait) 2'd3: lights = 8'b11111111; 2'd2: lights = 8'b00001111; 2'd1: lights = 8'b00000011; 2'd0: lights = 8'b00000001; default: lights = 8'bxxxxxxxx; endcase endendmoduleFour values of a 2-bit input cover every case, so the default can only be reached when gait contains an X or a Z. A real input pin is never "unknown", but in a simulation an input can be, for example when nothing drives it. The testbench tries that on purpose in its last line. Its five lines each carry an expect: comment, and the last one expects all eight lights unknown.
// Testbench for horse_gait: the four gaits, then a gait with an unknown bit.module horse_gait_tb(); logic [1:0] gait; logic [7:0] lights;
horse_gait dut(.*);
initial begin gait = 2'b00; #10; // expect: lights=8'b00000001 gait = 2'b01; #10; // expect: lights=8'b00000011 gait = 2'b10; #10; // expect: lights=8'b00001111 gait = 2'b11; #10; // expect: lights=8'b11111111 gait = 2'bx1; #10; // expect: lights=8'bxxxxxxxx $stop; endendmoduleTime in ps; one column is 10 ps.
Timing diagram from simulation, ch03/horse-gait-trace: time in ps from 0 to 50, 5 columns of 10 ps.
gait (2 bits, bin): 00 at 0, then 01 at 10, 10 at 20, 11 at 30, x1 (some bits unknown or high impedance) at 40.
lights (8 bits, bin): 00000001 at 0, then 00000011 at 10, 00001111 at 20, 11111111 at 30, unknown at 40.
Description: A timing diagram of gait and lights in binary over five steps. gait counts through 00, 01, 10 and 11, and lights shows a longer and longer row of ones. In the fifth step gait is x1 and lights is entirely unknown.
In Figure 3.1.5 the first four steps show the row of lights growing from one to eight. In the last step gait is x1, one bit unknown. The case compares the whole value bit by bit, and x1 is not equal to 00, 01, 10 or 11, so none of the four branches is taken and the default assigns X to all eight bits. The output is unknown even though one of the two input bits is known to be 1. The simulator does not guess.
The default line also matters for a second reason, covered in Section 3.9: when a module is turned into hardware, X on the right side of an assignment tells the tools that they may pick whatever output is cheapest for those inputs.
An aside: a testbench that checks itself
Section titled “An aside: a testbench that checks itself”ReadingBeyond the course: self-checking testbenches
In everything above, you look at the waveform and decide whether the output is right. A self-checking testbench decides for you: it compares each output with an expected value and reports mismatches, so a long test can be run without anyone reading a waveform. The one below tests mux2 exhaustively and counts the mismatches. It prints the count at the end, and the book's tools read that number: it is 1'd0.
// A self-checking testbench for mux2: it compares y with its own model of the mux and counts the mismatches.module mux2_selfcheck_tb(); logic s, d0, d1, y; int errors;
mux2 dut(.*);
initial begin errors = 0; for (int i = 0; i < 8; i++) begin {s, d1, d0} = i[2:0]; #10; if (y !== (s ? d1 : d0)) begin errors++; $display("mismatch: s=%b d1=%b d0=%b y=%b", s, d1, d0, y); end end $display("DD errors=%0d", errors); $stop; endendmoduleA self-checking testbench is only as good as its model of the right answer. Here the model, s ? d1 : d0, is the same expression the design uses, so a misunderstanding of the multiplexer would be copied into both. Real projects write the expected values independently of the design. Nothing else in this book uses one, and you do not need it to follow the rest of the chapter.
Course conventions
Section titled “Course conventions”How CSE 369 writes this
In CSE 369 we name a testbench after its module with _tb added, give it no ports, name the instance of the module under test dut, and end the script with $stop. Delays are written as #10 and so on, with no units; the simulator counts in its own time units. Every input gets a value at time zero.
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: they say nothing about how fast a real circuit is, and a label of ps does not mean a gate in a real chip takes that long. The book says this once per chapter, here, and does not repeat it.
Pitfalls
Section titled “Pitfalls”Common pitfall
- Giving the testbench ports, or forgetting to instantiate the module under test. The testbench is the whole world of the simulation, so it has an empty port list and the DUT is inside it.
- Leaving an input unassigned at time zero. An input nobody drives is unknown, and an output that depends on it can be unknown too. It need not be: an unknown AND 0 is 0, and an unknown OR 1 is 1, so another input can hide the X for a while. Assign every input in the first line of the script rather than relying on that.
- Changing inputs on consecutive lines with no delay and then looking for the in-between values. Changes at the same simulated time share one point on the time axis, so only the settled values show. Put a delay after each combination you want to see.
- Testing a small combinational module with a few favorite inputs. With few inputs, try them all.
- Treating a comment as a check. A comment says what you hoped for, and in your own testbench nothing reads it; only a run, and a look at the waveform or a self-checking test, shows what happened.
Check your understanding
Section titled “Check your understanding”Take the quiz (4 questions)
Which header does a testbench for a module adder use?
_tb. The second header is a module with ports, which a testbench never needs. The third is the module under test itself.A testbench for a combinational module has two lines in a row that each set inputs, with no # delay between them. What does the waveform show for an input that both lines set?
In the horse-gait testbench, what is lights when gait is x1?
case statement compares exact bit patterns. A pattern with an unknown bit equals none of the four item values, so the default runs.A module has five single-bit inputs and no state. How many steps does a testbench need to try every input combination?
Exercises
Section titled “Exercises”The module mux2 from this section is tested by a different testbench, in five steps of ten time units each. The figure shows the inputs. Work out y for every step.
Time in ps; one column is 10 ps.
Timing diagram from simulation, ch03/mux2-question: time in ps from 0 to 50, 5 columns of 10 ps.
s: 0 at 0, then 1 at 20, 0 at 40.
d0: 0 at 0, then 1 at 10.
d1: 1 at 0, then 0 at 30.
Empty lanes for the reader to complete: y.
Description: A timing diagram of s, d0 and d1, with the lane for y left empty.
Hint
s first: it says which of d0 and d1 the output follows.Solution
Take the steps in order, starting from the first line, which sets all three inputs at time zero.
- Step 1:
sis 0, soyfollowsd0, which is 0.yis 0. - Step 2:
d0has risen to 1, andsis still 0, soyis 1. - Step 3:
shas risen to 1, soynow followsd1, which is 1.ystays 1. - Step 4:
d1has fallen to 0, andyfollowsd1, soyis 0. - Step 5:
shas fallen back to 0, soyfollowsd0again, which is 1.yis 1.
Time in ps; one column is 10 ps.
Timing diagram from simulation, ch03/mux2-question: time in ps from 0 to 50, 5 columns of 10 ps.
s: 0 at 0, then 1 at 20, 0 at 40.
d0: 0 at 0, then 1 at 10.
d1: 1 at 0, then 0 at 30.
y: 0 at 0, then 1 at 10, 0 at 30, 1 at 40.
Description: The full timing diagram of s, d0, d1 and y. y is low, high, high, low, high over the five steps.
The module majority3 outputs 1 when at least two of its three inputs are 1.
// Majority of three: F is 1 when at least two of the inputs are 1.module majority3 ( input logic A, input logic B, input logic C, output logic F); assign F = (A & B) | (A & C) | (B & C);endmoduleWrite a testbench for it that tries every input combination, ten time units each, and ends with $stop.
Hint
for loop counting from 0 to 7 produces them in binary order, and the concatenation {A, B, C} on the left of an assignment spreads a number over the three signals, with A taking the top bit.Solution
The testbench follows the four parts in the order above: a module with no ports, a signal for every port, the instance named dut, and one initial block. The loop counter runs from 0 to 7. In each step, the low three bits of the counter are assigned to A, B and C together, and a delay lets the output settle and appear in the waveform.
// Testbench for majority3: all eight input combinations, counting up in binary.module majority3_tb(); logic A, B, C, F;
majority3 dut(.*);
initial begin for (int i = 0; i < 8; i++) begin {A, B, C} = i[2:0]; #10; end $stop; endendmoduleThe module's output is also checked against the majority function by the book's tools, from a specification written separately from this listing, so the listing is known to be a correct majority.
The module equal2 compares two 2-bit numbers, X1 X0 and Y1 Y0, and outputs 1 when they are equal.
// Compares two 2-bit numbers, X1 X0 and Y1 Y0: Eq is 1 when they are the same.module equal2 ( input logic X1, input logic X0, input logic Y1, input logic Y0, output logic Eq); assign Eq = ~(X1 ^ Y1) & ~(X0 ^ Y0);endmoduleWrite a testbench that tries every pair of values. How many steps does it take?
Hint
Solution
There are four values of X and four values of Y, so steps, the same as one loop over the four bits together. Two nested loops say it more clearly: the outer loop picks the value of the number X1 X0, and for each such value the inner loop runs through all four values of Y1 Y0.
// Testbench for equal2: every pair of 2-bit values, with the second number counting fastest.module equal2_tb(); logic X1, X0, Y1, Y0, Eq;
equal2 dut(.*);
initial begin for (int i = 0; i < 4; i++) begin for (int j = 0; j < 4; j++) begin {X1, X0} = i[1:0]; {Y1, Y0} = j[1:0]; #10; end end $stop; endendmoduleThe module is also checked by the book's tools against a specification that states equality of the two numbers directly.
In the horse-gait run, the first four steps give four different patterns and the fifth, with gait equal to x1, gives an unknown output. Without changing the module, which one input value could you add to the testbench so that the default branch is reached with every bit of gait known? Explain your answer.
Hint
Solution
There is none. A 2-bit input with no unknown or high-impedance bits has four possible values, 00, 01, 10 and 11, and the case has an item for each. Every fully known value matches an item, so the default branch can only be reached when gait contains an x or a z, as in the last step of Figure 3.1.5.
This is also why the default is harmless in hardware for this module: a real input pin is always at 0 or at 1.