This lab will introducee the basics of simulation, which is an essential tool for debugging and verification. Up until now, we have had to push our designs all the way through the tools and put in on a FPGA in order to verify them. Althought the circuits we have designed up to this point have been relatively simple, you will eventually be working with much larger designs. Tool times and other factors can make debugging designs of this size using only the FPGA quite impractical. In this lab, you will learn how to simulate a hardware design and write testbenches, both of which are essential in the verifcation process of large and complex systems.
Please make sure to complete the prelab before you attend your lab sections. This week's lab will be very long and frustrating if you do not complete the prelab ahead of time.
We have tried to wrap up as many of the tool details as possible in the build
scripts included in the lab distribution. All of the simulation-related files
are contained in the sim
directory. Go there now.
% cd ~/lab4/sim
Like in previous labs, the build system is primarily drive by the make
command. Just like XST, Modelsim must first compile your verilog files before it
can use them. To compile your files and make sure they all pass the syntax
checks, run
% make compile
Once you have your designs compiling, you need to run some test cases. The build
system looks inside the tests
directory for test cases to run. Each test case
is a .do
file, which is a script in Tcl, a scripting language used by a
variety of CAD tools. For the most part you don't need to worry about the
details of Tcl; you will just be using it to issue commands directly to
Modelsim.
Open up tests/Lab4BasicTestbench.do:
set MODULE Lab4BasicTestbench
start $MODULE
add wave $MODULE/DUT/*
run 10us
The first line sets the value of the variable MODULE
to Lab4BasicTestbench
.
Its value is referenced through the rest of the script as $MODULE
. The start
command tells Modelsim which Verilog module it should simulate.
The add
command is interesting. By default, Modelsim doesn't collect any
waveform information from the simulation. '*' is a shortcut for "anything", so
this particular command tells the simulation to collection info for all of the
signals in the module instantiated as DUT
in the Lab4BasicTestbench
module.
Finally, the run
command actually runs the simulation. It takes an amount of
time as an argument, in this case 10us
(10 microseconds). Other units (ns, ms,
s) are possible. The simulation will run for this amount of time. In most cases
this will serve as a timeout because your testbenches should cause the
simulation to exit (using the $finish()
system call) when they are done.
Let's try running the BasicTestbench simulation. To run all of the cases in the
tests
directory:
% make
This will first recompile your design if needed, then run the simulation. You
should see the output of simulation printed to your terminal. It will also be
written to results/<testcasename>.transcript
. You should see the following
line in the output:
# INFO[Lab4BasicTestbench @ 270000]: Test sequence completed
It is the result of a $display()
call made in the testbench.
Now for the fun part. After simulation completes you can view the waveforms for
signals that you add
ed in your test case script. The waveform database is
stored in .wlf
files inside the results
directory. To view them use the
viewwave
script included in the sim
directory.
% ./viewwave results/Lab4BasicTestbench.wlf
This will pop open a modelsim window that shows you a hierarichal view of the signals that your simulation captured.
You can either select a module in the list of instances (highlighted in green
above) and then add signals from the object window (highlighted in red)
individually by right clicking and choosing Add > To Wave > Selected Signals
or you can right click on an instance and choose Add > To Wave > All items in
region
. After adding signals a waveform pane should open.
Note: The middle of the three buttons pointed at by the orange arrow allows you to pop out an individual pane into its own window. You will most likely find this useful when viewing the waveform.
The wave window shows you the values of the signals you have selected over time. It is fairly straightforward. Zoom buttons are used the change the time resolution and are highlighted in green above.
Notice that you can select a signal by clicking on its name on the left. The
selected signal, Out
in this case, is highlighted in a lighter color. You can
also select a specific time by click on the wave form. The time is indicated by
a vertical yellow bar, called the cursor. The values of each of the signals
displayed on the left are for the time that you have selected are for the time
that you have selected with the cursor.
Finally, notice the the portion of the toolbar highlighted in red. These are the
find buttons. They let you find transition for a selected signal. For instance,
in the above diagram, choosing the Find next transition
button will move the
cursor to the next time the Out
signal changes after 33120 ps. You can also
search backwards or only for low-to-high or high-to-low transitions.
Modify the Lab4BasicTestbench such that the inputs to the Lab4Basic DUT change in such a way that the output signal from the DUT repeats with a period of 3 cycles. Use Modelsim, specifically the waveform viewer, to help determine the shape of input signal.
Recall that
#(Cycle)
will delay the simulation for a cycle. Also keep in mind that
`timescale 1ns/1ps
Means that #()
uses units of nanoseconds.
Every time you edit your verilog, run
make
and then press the refresh button on the wave window in order to see the
new results.
To test the functionality of a purely combinational circuit, we can apply all possible inputs by brute force and check the output for each combination. This is called exhaustive testing and is useful for verifying purely combinational circuits with a relatively modest number of inputs. In this section, we will apply exhaustive testing by permuting through all possible inputs to a broken 8-bit adder implemented in Lab4Adder.v. This 8-bit adder will produce the correct result almost all the time, but will output the wrong value under server specific input combinations. The port specification for the Lab4Adder module is given below.
Signal | Width | Dir | Description |
---|---|---|---|
A |
8 | In | Input A to the broken adder |
B |
8 | In | Input B to the broken adder |
Sum |
8 | Out | Output the sum of A and B |
The overall layout of the testbench is shown in the figure below. Using Lab4BasicTestbench as an example write a new testbench, Lab4AdderTestbench.
This testbench should run through all the possible input combinations to the 8-bit adder, and detect improper results using a simulation model you have written for a correctly functioning adder.
You should use the $display()
system call to display when you find an error in
the adder. Here is an example:
$display("Error at time %t: got %d, expected %d", $time, DUTOut, ModelOut);
You will need to create a new .do file in the tests
directory to actually run
the simulation. Use the basic testbench as an example. If you would like to run
only one test at a time (say Lab4AdderTestbench.do), use the following
invocation for make:
% make results/Lab4AdderTestbench.transcript
Instead of using a loop to generate every possible input combination. You are
now going to generate random test inputs, and test the broken adder with these.
The simulation should stop when it detects the first error, you may find the
verilog system calls $random
and $finish
useful.
The state transition diagram for a finite state machine you would like to implement is given below. This state machine's function is to output a 1 whenever it sees that the last four inputs were 1011.
In this section, you will be writing a testbench to map out the stat transition table of Lab4FSM, an implementation of the given state transition diagram, but with some incorrect transition arcs. The port specification for Lab4FSM is given below
Signal | Width | Dir | Description |
---|---|---|---|
Clock |
1 | In | The clock for our state |
Reset |
1 | In | Reset state to default |
In |
1 | In | The input to the FSM |
Out |
1 | Out | The output from the FSM |
State |
3 | Out | An indication of the current state |
With the state encoding given below,
State | Encoding |
---|---|
S0 | 3'b000 |
S1 | 3'b001 |
S2 | 3'b010 |
S3 | 3'b011 |
S4 | 3'b100 |
Since it is rather tedious to manually set signal values, advance simulation time, then set another signal value repeatedly. You will instead read a sequence of inputs from a file, and have the testbench automatically apply the inputs and advance simulation time. Essentially, your testbench should perform the following.
1. Read all test input sequences from input file into a reg array.
2. Apply the Reset signal to the FSM and advance simulation time by one
cycle.
3. Note the initial state of your FSM.
4. Systematically apply each bit of the input sequence to the FSM, remember
to advance simulation time by 1 cycle after each input.
5. When finished applying the current input sequence, Reset the FSM and
move on to the next input sequence.
As an example, if the first input sequence is the 8-bit number 10110010, the
testbench should apply a 1 as the input to the state machine the first cycle, 0
on the next cycle, then 1, 1, 0, 0, 1, and finally 0. The testbench should then
reset the state machine and apply the next input sequence. To read input
sequence from a file you will need to use the $readmemb
Verilog system call,
and example of how to use $readmemb
to read a file into a reg array is given
below.
// Integers can be used to index an array, or as a for loop count they may not
// be used in synthesis
integer i, j;
// Declare an array of 8-bit values. It contains 16 elements indexed 0 to 15.
// Note that it is declared as reg, since we set its value inside of an
// initial block.
reg [7:0] TestValues [0:15];
initial
begin
// Read the file TestValues.txt and put the values
// in TestValues array
$readmemb("TestValues.input", TestValues);
//Outer for loop iterates through each 8-bit chunk
for (i = 0; i < 16; i = i + 1)
begin
//Inner for loop iterates through each bit
for (j = 0; j < 8; j = j + 1)
begin
$display("TestValues[%d][%d] = %b", i, j, TestValues[i][j]);
end
end
end
An example of TestValues.input is given below,
10110110
10101011
11010001
11110001
11010101
00110110
Note that for input files to be picked up by the build system, they must end in
.input
and they must be placed in the tests
directory.
Note there are some extra precautions you must take when generating a clock signal. Here is an example of the correct way to do it:
reg Clock;
initial Clock = 0;
always #(HalfCycle) Clock = ~Clock.
There are a couple of notable things:
1. Using the initial
block to set the clock to 0 at the beginning of the
simulation. You must start the clock at 0, otherwise you will be trying to
change inputs at the same time the clocks changes and it will cause strange
behavior.
2. You must use an always
block without a trigger list to cause the Clock to
change by itself.