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
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
% 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
.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
Open up tests/Lab4BasicTestbench.do:
set MODULE Lab4BasicTestbench
add wave $MODULE/DUT/*
The first line sets the value of the variable
Its value is referenced through the rest of the script as
command tells Modelsim which Verilog module it should simulate.
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
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
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
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
added in your test case script. The waveform database is
.wlf files inside the
results directory. To view them use the
viewwave script included in the
% ./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
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.
will delay the simulation for a cycle. Also keep in mind 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
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.
||8||In||Input A to the broken adder|
||8||In||Input B to the broken adder|
||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
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
||1||In||The clock for our state|
||1||In||Reset state to default|
||1||In||The input to the FSM|
||1||Out||The output from the FSM|
||3||Out||An indication of the current state|
With the state encoding given below,
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
// 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];
// Read the file TestValues.txt and put the values
// in TestValues array
//Outer for loop iterates through each 8-bit chunk
for (i = 0; i < 16; i = i + 1)
//Inner for loop iterates through each bit
for (j = 0; j < 8; j = j + 1)
$display("TestValues[%d][%d] = %b", i, j, TestValues[i][j]);
An example of TestValues.input is given below,
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
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:
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
2. You must use an
always block without a trigger list to cause the Clock to
change by itself.