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
|