UC Berkeley CS150

Lab 4, Circuit Simulation & Testing

Introduction

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.

PreLab

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.

  1. Read this entire handout thoroughly.
  2. Download the lab files: lab4.tar.gz
  3. Write your Verilog ahead of time. You will need to modify Lab4AdderTestbench.v and LAb4FSMTestbench.v.
  4. Answer the PreLab questions

Lab Procedure

Modelsim Tutorial

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.

Viewing Waveforms

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 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.

Modelsim Window

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.

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.

Editing Basic Testbench

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.

Broken Adder Exhaustive Testing

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.

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

Broken Adder Randomized Testing

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.

Finite State Machine Mapping

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.

FSM Mapping

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.

Questions

Prelab

  1. Lab4BasicTestbench generates a Clock signal at what frequency?
  2. Explain what modifications you must make to the testbench to halve the frequency of the Clock signal that it generates.
  3. While only looking at the provided code in LAb4BasicTestbench.v, complete the a timing diagram for the inputs (In, Reset) during the first 10 cycles of simulation. Use this as a template.

Lab

  1. Using Modelsim's wave viewer, complete the timing diagram from Prelab (3) with the remaining signals during the first 10 cycles of simulation.
  2. Write down the inputs and output of the cases that the broken adder fails.
  3. How much simulation time passed before you found the first error?
  4. When might a random test be preferable to an exhaustive test?
  5. Draw a state transition diagram for the FSM.
  6. What input string does the broken FSM detect?