Logic Design - Testbenches and Simulation in Verilog

[Edit of Image1]

Introduction

Hey it's a me again @drifter1!

Today we continue with the Logic Design series on Verilog to get into Testbenches and Simulation.

So, without further ado, let's get straight into it!


Testbenches

Verifying and validating the functionality of a design by testing it, is as important as designing it. And it's a quite tedious and complex process. Coming up with the right test cases, understanding the specs of the design... it's a complete branch on its own!

Device or Unit Under Test (DUT or UUT)

First of all, the design that is tested using a testbench is referred to as a device or unit under test (DUT or UUT). Thus, the design's module is commonly given the name dut or uut (sometimes in caps) when instantiated from within the testbench, as shown below.

design_name DUT( port mapping );

Testbench Syntax

A testbench is nothing more than another Verilog file. But for the purpose of testing and simulation of course all of Verilog can be used (even non-synthesizable constructs), as the testbench is not meant for synthesis. It's thus possible to use initial blocks, and while, forever and repeat loops, which normally don't synthesize, as well as always blocks, conditional statements and/or for loops, which can be synthesized. Verilog also provides us with so called System Tasks, which can be used for text output, file I/O and simulation control.

The testbench is a module without I/O (there's no port list in it's definition), as it's not a design description. Its common to give it the same name as the design followed by a "_tb". The outputs of the DUT are defined as wire types, whilst the inputs are defined as reg, and connected to the corresponding ports of the DUT.

Thus, the main outline of a testbench is:

module design_tb;
    reg inputs;
    wire outputs;

    design UUT( port mapping );

    // main testing and simulation

endmodule

Test Cases

It's common to include an initial block that initializes the design's inputs to some value (such as all zeros), as shown below.

initial begin
    // initialize or reset inputs
end

This can also be a form of Reset Test, which checks if reseting after some cycles have passed truly resets the design.

initial begin
    // initialize inputs, but don't reset design

    // delay for N cycles
    #N

    // reset design
end

Of course it's also possible to do the same for enable signals (Enable Test), or other such control signals. After initialization, the design can then be tested by applying specific inputs for a specific time frame, outputing or comparing the output to the correct value, etc.

Clock Generator

In the case of sequential circuits, which are synchronous by nature, there is always a clock signal. Simulating the clock is very easy, and requires only:

  • an initial block that initializes the clock signal, and
  • an always block that flips the value of the signal after some delay

If clk is a clock signal, then the Verilog code for a clock generator looks like this:

initial begin
    clk = 0;
end

always begin
    #1 clk = ! clk;
end

Timescale

Let's also not forget to mention the timescale. The timescale is defined at the beginning of a Verilog file and tells the simulator how much time #1 specifies. It's a simple compiler directive, which is put at the beginning of the file and has the following syntax:

`timescale time_unit/time_precision

It's common to use something like:

`timescale 1ns/1ps

which basically means that #1 specifies the passing of one nanosecond (ns) of time with a precision of one picosecond (ps).


System Tasks

System tasks can be used in order to generate input and output during simulation. They all start with a dollar sign ($) and are ignored during synthesis. Thus, its safe to use them even in synthesizable design models.

Text Output

The following system tasks can be used in order to output text into the console of the simulator (be it ModelSim, Verilator, GTKWave etc.):

  • $display() : output and move to next line
  • $write() : output without moving to next line
  • $strobe() : output after the conclusion of all simulation events
  • $monitor() : output every time one of its parameters changes

There are also binary, octal and hexadecimal versions of these tasks, such as $displayb(), $writeo() or $monitorh(), which can also be useful. The output is enclosed within double quotes (" ") and text formatting codes such as %b, %o, %h, %d, %t, %s can be used in order to print out the values of wires, registers etc. using their identifier in a different string or number format. So, it's a C-like printf() syntax.

For example, if wire a has a decimal value of 4, printing out in various ways yields:

system task             output
$display(a)             // 00000000000000000000000000000100
$displayh(a)            // 4
$write("a = ", a)       // A = 00000000000000000000000000000100
$write("a = %h", a)     // A = 4

By default, all of the system tasks print out in binary, so it's like using the b-terminated variants. Most simulation tools also print out 4 bytes or 32 bits.

File I/O

In order to create, read, write and/or modify files, various system tasks can be used, such as:

  • $fopen() : open file (with "r", "w", "a" etc. parameter)
  • $fclose() : close file
  • $fdisplay() : similar to $display but the output is written to a file
  • $fwrite() : similar to $write but the output is written to a file
  • $fstrobe() : similar to $strobe but the output is written to a file
  • $fmonitor() : similar to $monitor but the output is written to a file
  • $readmemb() : read binary data from a file and store into a memory array
  • $readmemh() : read hexadecimal data from a file and store into a memory array

The last two are quite useful for loading in testing data, ROM, RAM values etc. The remaining tasks are mainly useful for outputing the results to a file instead of the console.

For example, let's say a design includes a memory array with the name mem and that a binary file called mem.data (with corresponding testing or initialization data) exists in the root directory. It's possible to initialize the memory array for simulation purposes using $readmemb within an initial block as follows:

initial
begin
    $readmemb("mem.data", mem);
end

Simulation Control

In order to control the way the simulation proceeds, or to check the current simulation time, the following system tasks can be used:

  • $reset : resets the simulation back to time 0
  • $stop : pauses the simulation
  • $finish : terminates the simulation
  • $time : outputs current simulation time as a 64-bit vector
  • $stime : outputs current simulation time as a 32-bit integer
  • $realtime : outputs current simulation time as a 32-bit real

For example, one might include an initial block with a specific delay followed by a $finish to terminate the simulation after that delay:

initial begin
    #100 $finish;
end

Other

Verilog also includes other system tasks, which can be useful. One of them is $random() with an optional seed value (otherwise the current computer clock is used), which generates a random integer each time it's called. Of course, using the same seed allows us to make the sequence repeatable.

During simulation some simulation tools require additional information. Verilog provides us with the following system tasks for that purpose:

  • $dumpfile() : dump variable changes (nets and registers) to a file given as parameter
  • $dumpvars : specify which variables should be dumped (without arguments all variables are dumped)
  • $dumpall : specify that all variables should be dumped
  • $dumpon : specify when dumping should begin
  • $dumpoff : specify when dumping should stop

It's wise to include the system tasks $dumpfile and $dumpvars within an initial block when using most of the known simulation tools:

initial begin
    $dumpfile("dump.vcd");
    $dumpvars;
end

Simulation Tools

ModelSim

During my studies we used ModelSim for the purposes of simulation. It's a quite advanded simulator with a GUI and console. It has a free version which should be more than enough for educational use!

There's an old guide on the blog for that software which you can find here. It should get you going if you want to use it. I think it's worth mentioning though, that vlog should be used instead of vcom in order to compile Verilog files, instead of VHDL ones, when using the terminal approach.

GTKWave

Then there's GTKWave. An open-source GUI and/or terminal simulator, which can easily be used within VMs such as WSL, Cygwin etc. It's great when used together with compilers such as the ones mentioned in the next section.

Compilers

Simulators commonly use Icarus Verilog, Verilator and other similar (but commercial) compilers in the back-end, which compile Verilog and SystemVerilog into C++ or SystemC. The resulting program can easily be used in software such as GTKWave, in order to view the simulation and its results.

EDA Playground

Lastly, I think it's also worth mentioning online tools such as EDA Playground, which can get you going easily. Simply start off from some Example project and make your way from there.


RESOURCES:

References

  1. http://www.asic-world.com/verilog/veritut.html
  2. https://www.chipverify.com/verilog/verilog-tutorial
  3. https://www.javatpoint.com/verilog

Images

  1. https://www.flickr.com/photos/creative_stock/5227842611

Block diagrams and other visualizations were made using draw.io


Previous articles of the series


Final words | Next up

And this is actually it for today's post!

Next time we will get into testbench examples and simulate them using the various tools mentioned!

See Ya!

Keep on drifting!

H2
H3
H4
3 columns
2 columns
1 column
Join the conversion now