VHDL Testbenches


Author's Note: The following is a simple introduction to VHDL testbenching. There is far more complexity possible, but the following should provide the basis for how to create and use a testbench to thoroughly test your designs. For complete examples, you should look at the testbenches provided with each of the labs. They are good examples of everything discussed in this document


I. Simulator

    A. Functionality


  • Updates to signals are made during delta cycles: very small time units
  • Combinatorial signal assignments, outside processes, are updated every delta cycle

    B. Advantages

  • No need to synthesize and implement your design to test. Simply edit, recompile and reload the simulator
  • Better debugging. Rather than hoping an LED will light up, you can see the signals that you are using

    C. Disadvantages

  • A working simulated design does not necessarily imply a working hardware design
  • With just a simulator, it is tough to control your signals => testbenching

II. Clock

    A. Simulating


  • Very rarely will the clock not be a part of a testbenched design. It is used to synchronize the stimulus that you will provide your design and also serves as the clock for the design itself
  • Generally, the clock is set to an initial value and inverted at regular intervals based on a constant, as below. The condition allows you to continue simulating the clock only so long as you are simulating, thus letting the simulator know that the simulation is over

constant clk_period : time := 5 ns;
     -- set clock period is 5 nanoseconds
. . .

clk <= not clk after clk_period [when <condition> else <value> ];
     -- clock changes every clk_period
     -- condition allows you to stop the clock when 
     -- the simulation is over

    B. Application


  • Using a clock in a testbench, as with any other use, requires knowledge of edge-specification. The way that synchronization occurs with the clock is that the device will make some assignment on a falling or rising clock edge. This is determined as below:

if clk'event and clk = '1' then
     -- essentially, if rising edge, then...
     -- for falling edge, use "...clk='0'..."
. . .


  • Most often, you will want to set the inputs and then give them some time to settle before checking them. This can be accomplished by having one process stimulating them on the rising edge of the clock, and another stimulating them at the falling edge

III. Reporting Results

    A. Assert


  • A common way to write a self-checking testbench is with assert statements. Just like in other programming languages, assert statements in simulated VHDL will check a condition and, upon failure of that condition, report a state
  • Asserts are generally followed by a report statement, which prints a string report
  • Report statements can be followed by a severity clause. This denotes how serious the failed condition was. Severity is an enumerated type with four enumerations: note, warning, error, and failure

assert <condition>
report "<report_string>"
[severity {note|warning|error|failure}];


    B. Reports


  • Report statements do not necessarily need to be accompanied by assertions. You may contain a report within an if-then-else structure

-- this is a cooked example
-- it is unlikely that some maximum would be tolerated


if err_num > MAX_ERRORS then
-- errors are too numerous
report "Too many errors, simulation aborted"
severity failure;

elsif err_num <= MAX_ERRORS then
-- errors, but not too many
report "Errors detected"
severity error;

else
-- no errors
report "No errors found"
severity note;


    C. Text Output


  • Unfortunately, you cannot use report statements to display signals within your design, you must use another method: I/O
  • You must declare a 'line' variable in the process in which you wish to print these statements. Next, you can write to this line and, in turn, write the line to standard output

variable line_out: line;

...

write(line_out, now);
    -- now stores the current simulation time
write(line_out, string'("<string>"));
write(line_out, <other_signal>);
...
writeline(output, line_out);
    -- writeline flushes everything in the line to output


IV. File I/O

    A. Libraries


    There are a couple of libraries you will need for testbenches in general:

  • ieee.std_logic_1164.all
  • ieee.std_logic_textio.all;
  • ieee.numeric_std.all;

  • std.textio.all;

    B. File Input

  • Like text output above, file input is done with lines. You must, however, declare the file to be outputted so that the simulator knows what to read
  • Also like above, you read the file one line at a time. From the line, then, you can grab the input and store it into a signal. To do this, you use 'read' or 'hread' functions

variable line_in: line;
file input_file: text is "<filename>"

...

readline(input_file, line_in);
read(line_in, <variable>);
  -- for a normal signal
hread(line_in, <variable>);
  -- for a hexadecimal input from the file


  • File output is done with the corresponding write functions, just as standard output is done


[Return to CMPUT 329 Lab Home Page]
Created by Paras Mehta, 2003