Professional Documents
Culture Documents
Verification is an important part of any ASIC design cycle. It's important that complex designs are
simulated fully before prototypes are built, as it's difficult to find bugs in silicon and going through
additional layout cycles is costly and time consuming.
VHDL is well suited for verification. This course is an introduction to VHDL verification techniques. It
assumes some familiarity with VHDL.
Table of Contents
Sponsor: BIO-SMG
y <= (x1 and not x2) or (x2 and not x1) or (x1 and x2);
It's possible to generate stimulus using a process with signal assignments and wait statements as shown
below:
test_seq: process
begin
x1 <= '0';
x2 <= '0';
wait for 10 ns;
x1 <= '1';
x2 <= '0';
wait for 10 ns;
x1 <= '0';
x2 <= '1';
wait for 10 ns;
x1 <= '1';
x2 <= '1';
wait for 10 ns;
x1 <= 'X';
x2 <= 'X';
...
This code can now be simulated and it's possible to identify the design error by observing the
waveforms:
However it's more efficient to build in checks which automatically verify the result of a simulation. This
can be accomplished with the use of assert statements. An assert statement verifies that a certain
condition holds true. If the condition is violated it will generate the associated message and will attach
the specified severity level to it. The example VHDL code could be extended like this:
....
x1 <= '1';
x2 <= '1';
assert y = (x1 xor x2)
report "E@TB: circuit failed"
severity Error;
...
Ideally the assert statement should provide information about the condition in which the error occurred.
This will aid debugging substantially. Below are two alternatives to the previous assert statements. One
uses the 'image attribute from the VHDL 93 standard. The other uses the conversion function str() from
package txt_util.vhd which is used extensively throughout this course. The package provides a
somewhat less cumbersome way to handle string processing and similar tasks.
Shown below is the output of the two assert statements. It's now much easier to identify the cause of the
problem.
A standard format for error messages will also help to identify the location of a bug. The standard
adopted here uses the first letter to indicate the severity (I=Information, W=Warning, E=Error,
F=Failure) followed by "@" and the entity name of the unit which generated the message. The "E@"
notation makes it very easy to grep long logfiles and identify problems. Knowing the entity which
detected a bug will aid fixing it, too.
Below are the files which have been simulated in this section:
txt_util. tb1.
vhd vhd
Testbench Structure
In the previous section both the design and the test code were located in the same file. This approach
was only taken for demonstration purposes and is not recommended in the general case. Typical
testbench code - such as text output and assert statements - can not be synthesized and will at the very
least create a number of unnecessary warnings from the tool. Also testbench code can have similar
complexity as design code and if it's well structured, is quite likely to be reusable.
In this example the top level entity (testbench) instantiates four components: the processor transactor,
the RAM model, the I2C-Controller transactor and the design which needs to be tested (DUT). Each
testbench component and the top level testbench are in their own files, the DUT is also in a separate file.
The testbench components produce stimulus and verify the response from the DUT.
Definition of Terms
The following terms are used frequently in literature. Despite of the statement in the heading, there are
no hard definitions, but the following should reflect common usage:
● Model
A model is a description of the device which behaves just like the device does. Generally the
behaviour can not be controlled by any other way then applying stimulus to it's input pins. RAMs
are usually described in this way.
● Transactor
Transactors have additional control mechanisms: they can read control data from a file or contain
test specific code. They control which test is run in the testbench in which they are instantiated.
Writing to Files
Writing to files can be useful in a VHDL simulation. This section will illustrate how to implement that
with VHDL 93.
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use work.txt_util.all;
entity FILE_LOG is
generic (
log_file: string := "res.log"
);
port(
CLK : in std_logic;
RST : in std_logic;
x1 : in std_logic;
x2 : in std_logic_vector(7 downto 0)
);
end FILE_LOG;
For the purpose of this example two signals x1 and x2 shall be logged to a file on every rising clock
edge.
begin
Here l_file is the file, it's of type TEXT and opened in write mode. The log_file parameter is of type
The following loop will log x1 and x2 into the file specified by log_file:
As can be seen VHDL writes to a file in two steps: first a string is written to a variable of type line (that's
what the write command does), then the line is appended to a file with the writeline command.
In the example shown here x1 is of type std_logic and x2 of type std_logic_vector. The function str()
will convert x1 in a string, the function hstr() will convert x2 in a string in hex format. (Both functions
are in txt_util.vhd). Strings and characters can be combined into a larger string with the & operator.
The txt_util package provides a simpler way to write to a file, e.g. with:
Which fits into a single line and doesn't require a line type variable. Also since the input to print is
always a string no type casting is necessary.
The usage can be illustrated by inserting the lines below in front of the while-loop. (The code will
generate a header for the log file.)
# x1 x2
#----------
0 01h
0 02h
0 03h
...
0 0Fh
1 10h
1 11h
...
Below are the files which have been simulated in this section:
entity FILE_READ is
generic(
stim_file: string :="sim.dat"
);
port(
CLK : in std_logic;
RST : in std_logic;
Y : out std_logic_vector(4 downto 0);
EOG : out std_logic
);
end FILE_READ;
In this example data is read from a file sim.dat at every rising clock edge and applied to the output
vector Y. Once every line of the file is read the EOG (End Of Generation) flag is set.
begin
The file is stimulus, it's of type TEXT and opened in read mode. The file name is defined in the string
stim_file. (stim_file is a generic, defined in the entity header).
Just as a file write, a file read is done in two steps. The first step fetches a line from a file and stores it in
a line type variable (readline command) the second reads a string from the line (read command):
end loop;
wait;
Since a string is read from the input file, a conversion function is required to obtain a std_logic_vector.
The function to_std_logic_vector(s) achieves that, it is part of the txt_util package.
00010
00011
11100
1UXZW
HL111
11111
Below are the files which have been simulated in this section:
The new version will read hex data and will also understand a command: #count. Each time it is found in the input file the
file reader shall shall count from 1 to 5 in binary format and present that count on the output port. Unfortunately the file I/O
of VHDL is not very sophisticated. It's not allowed to read a string from a file where the string is longer than the number
of characters in that line.
Here is what needs to be done in order to read variable length strings from an input file:
readline(stimulus, l);
s := (others => ' ');
for i in s'range loop
read(l, c, in_string);
s(i) := c;
if not in_string then -- found end of line
exit;
end if;
end loop;
The read function will return false for in_string once the last character of the line has been read.
The above function has been placed in txt_util.vhd and named str_read(stimulus, s). The length of s determines the
maximum number of characters in a line which can be evaluated.
Using this function the following code will implement the set task:
end loop;
wait;
Note that the appropriate sub-section of the string s needs to be compared with #count as comparing different length
strings will always yield the result false.
00010
00011
#count
11100
1UXZW
HL111
11111
Below are the files which have been simulated in this section:
The perl script below will generate an input file which can be read by file_read.vhd.
print "00011\n";
print "11100\n";
for ($i=0;$i<10;$i++) {
print num2binary($i,5)."\n";
}
print "1UXZW\n";
print "11111\n";
00011
11100
00000
00001
00010
00011
00100
00101
00110
00111
01000
01001
01010
1UXZW
HL111
11111
It's straightforward to extend this approach e.g. for 256 iterations if all values of a 8 bit word are to be
covered. Entering these values manually would be very cumbersome. The script actually calls a
procedure num2binary which can be found in the complete script. More complex procedures like PRBS
patterns or CRC generators could be used in a similar fashion.
tgen.
pl
SRAM modeling
SRAM models are used quite frequently and many devices have bus interfaces which are similar to SRAMs. It's therefore
valuable to have a standard approach for modeling these interfaces.
For the purpose of this example reading from the SRAM is ignored, and only writes are implemented. It's easier to code
transactors rather than models, so initially this approach is taken.
Core of the implementation is test_prg, a process which contains the write procedure and the test program. The purpose of
the write procedure is to verify the timing of a write access to the SRAM as well as to verify whether data and address are as
expected.
The parameters wadd and wdat specify address and data for the expected write access.
begin
The data bus is assigned to 'Z' (=tristate) values. This means the SRAM model will not drive the data bus and therefore will
not corrupt the data input. The procedure will wait for the start of the write access which is equivalent to waiting for WE_L
to be asserted (active low).
start_cycle := now;
The variable start_cycle will be assigned to the simulation time. This means it will contain the time at which WE_L was first
asserted in the access cycle. (The contents of that variable will be used later.) The attribute 'last_event is very useful for
testbenches, it returns the time which has expired since the last event of a signal. (Example: if WE_L was asserted at 35 ns,
and the current simulation time is 73 ns, then WE_L'last_event will return 73 ns - 35 ns = 38 ns.)
In this case the A'last_event returns the time A has been stable before WE_L was asserted. This is the actual setup time and
should be greater or equal to tSU. If this is not the case, the assert statement will issue the message "E@SIMPLE_SRAM:
Address setup time violated". The same mechanism is used to verify the setup time for the data lines.
In any case the access to the SRAM will be reported. The hstr function is part of the txt_util.vhd package, of course. It
returns the value of a std_logic_vector in hex format.
-- verify address
assert A = wadd
report "E@SIMPLE_SRAM: Address incorrect, expected "&
str(wadd)& " received "& str(A)
severity Error;
Next the address is verified, if it's incorrect a message is issued which reports the expected and the actual value. This time it's
in binary format, so that it's easier to identify which bit was corrupted.
-- verify data
for i in wdat'range loop
if wdat(i) /= '-' and wdat(i) /= D(i) then
print("E@SIMPLE_SRAM: Write Data Invalid, written data = "& str(D)&
" expected data = "& str(wdat) );
exit;
end if;
end loop;
Somewhat more effort is put into verifying the data bus. Bits which are marked with "-" (=don't care bits) in the expected
data are not compared with the actual data. This can be useful especially when modeling devices with a SRAM like interface
when not all control bits at a particular address are actually relevant. (Example: wdat="00010--" and D="0001011" => not
error would be indicated.)
The loop will go through all bits of the expected data word, compare or skip it and issue an error message when a
discrepancy is found. In that latter case the loop is exited, to avoid reporting the same error several times.
The cycle completes when WE_L is deasserted. Now it's possible to verify the pulse width by comparing the current time
with the time at the beginning of the cycle (= start_cycle). The 'last_event attribute can not be used for this purpose since
that would now refer to the time expired since WE_L returned to '1' (= 0 ns).
It's now also possible to verify that A hasn't changed during the cycle. If that was the case then A'last_event would be smaller
than the time which has expired since the beginning of the cycle (= now - start_cycle). The same steps are taken to verify
that D hasn't changed.
end write;
The cycle is not totally complete yet, as the address and data hold times have not been verified. The code above activates
two separate processes (the hold time monitors) to check the values of A and D.
As long as add_hold is true (which it will be for a time tH after WE_L was deasserted) the hold time memory will verify the
value of A, each time an event on A occurs. The simulation diagram below shows how the signals add_hold and dat_hold are
true (activating the hold time monitors) for 3 ns (the value of tH) and then return to false (deactivating the monitors).
Below are the files which have been simulated in this section:
One of the problems of implementing RAM models is representing internal memory. A large array of std_logic_vectors
(eg. 2Mx64 bits) needs a large chunk of memory on the machine it's simulated on.
A possible approach is to represent the memory cells with integers (of an appropriate range) rather than std_logic_vectors.
(One such approach can be found on Ben Cohen's website.) The approach taken here is based on the observation that most
tests only access a tiny fraction of the modeled memory. For each write access the data as well as the address is stored into
the internal array. For a subsequent access the model searches the array and identifies whether the address is already
present. So if there are only 32 different addresses which are accessed, then there is no reason to have more than that
number of memory locations in the model. However searching the array is more time intensive than using the address as an
index, therefore there is likely to be a cross over point, where searching requires more resources than having a large array.
-- Internal Memory
type mem_add_type is array (integer range <>) of std_logic_vector(A'range);
type mem_dat_type is array (integer range <>) of std_logic_vector(D'range);
The parameter mem_words is a generic defined in the header, it can be set during instantiation for the required number of
memory locations.
One of the additional tasks for the passive memory model, is to decide whether to call the read or the write procedure. The
code below will take care of that:
assert (WE_L /= 'X' and WE_L /= 'Z' and WE_L /= 'U' and WE_L /= '-') or
no_reset_yet
report "E@SRAM2: WE_L="& str(WE_L)& " invalid value"
severity Error;
assert (RD_L /= 'X' and RD_L /= 'Z' and RD_L /= 'U' and WE_L /= '-') or
no_reset_yet
report "E@SRAM2: RD_L="& str(RD_L)& " invalid value"
severity Error;
The process will wait until activity either on RD_L or WE_L occurs. Illegal values for these signals ('U', 'X', '-' and 'Z') are
reported as errors, they should never occur during simulation. Also if both signals are asserted simultaneously an error is
reported. The function to_X01 will convert 'H' and 'L' values to '1' and '0' respectively. This is useful on busses which are
pulled up or down and reflects the actual behaviour of the SRAM.
The write process is similar to the one for the transactor version. However the address and data verification can no longer
be performed. Also the write access now has to be stored in the memory array:
...
wait until to_X01(WE_L) = '1';
end if;
if mem_add(i) = A then -- access to an existing address
mem_dat(i) := D;
exit;
end if;
end loop;
The code loops through the already written array until a match for the address is found. If no match can be found and there
is still room in the array, the new address is entered and the usage pointer is incremented. If there is no more space
available a warning is issued.
The loop will investigate all written memory locations and try to establish a match. If successful it will take the data from
that location and drive it onto the bus, otherwise it will issue a warning and drive all X's on the data bus.
Below are the files which have been simulated in this section:
Signal Monitors
Often it's desirable to monitor the status of a signal and display messages whenever it changes. A good
example for this is an interrupt signal. Here presented are two possibilities for implementing this:
monitor: process(INT_L)
begin
The process will be executed each time there is an event on INT_L. Whenever that happens a message
will be printed.
Here is an alternative using an extensions of the print command which is available in txt_util.vhd:
This function has as a first parameter a boolean expression and as the second parameter a message text.
The message text will be printed whenever the boolean expression is true. (In this case whenever INT_L
changes to '0'). The function does not need to be part of a process, it can be used as a concurrent
statement.
Below are the files which have been simulated in this section:
txt_util. simple_mon.
vhd vhd
. . .
signal clk: std_logic := '0';
signal rst: std_logic;
begin
Note that the clk signal needs to be initialized in the declaration, as the inverse of 'U' (=uninitialized) is
also 'U'. Here is the simulation result:
Below are the files which have been simulated in this section:
tb_clk_rst.
vhd
The transactor reads commands from an input file and executes them, creating stimulus and verifying
responses for the testbench. For each simulation the input files for the simulated test need to be copied
into the working directory. Usually this method is combined with a pre-processor which generates the
input files, as described in the perl section.
● Usually the input file format does not support sophisticated structural elements (loops,
procedures etc) and VHDL is not really suited to implement a sophisticated parser
● Syntactical errors in the input file are usually only found during simulation time (this may result
in the loss of valuable computing time)
● Lack of feedback: it's usually not possible to react flexibly on the response of the DUT by
examining the value of signals etc
● The input "language" is not standard VHDL and therefore not immediately understandable to
other designers
● The testbench VHDL can remain clean and should run on all platforms without any changes
● The testbench structure is straightforward, no configuration statements are required
vcom precompiled_transactor.vhd
The file test_code.vhd is assumed to be in the current directory. (The compilation command would be
appropriate for the MTI simulator, it needs to be replaced by an equivalent command for other
simulators.)
● Tests can be compiled before simulation, so syntax errors are detected before the simulation runs
● Tests can be readily understood by designers who are familiar with VHDL
● All structural elements of VHDL are available
● Tests can react flexibly on the response of the DUT, full access to all signals known to the
transactor is possible
Below are the files which have been executed in this section:
Test-specific Entities
For each test there is a separate testbench which uses test-specific transactors. For example there may be
a microprocessor transactor mp.vhd. The testbench tb_test1 would then instantiate a test-specific
microprocessor transactor mp_test1.vhd which would be coded by enhancing the mp.vhd template.
● Bug fixes in the transactor code may have to be made in many files
● Results in a large number of files which are difficult to handle
● Tests can react flexibly on the response of the DUT, full access to all signals known to the
transactor is possible
In this example a circuit has been synthesized already, and the simulation shall verify the VHDL description of the
synthesized design. The following process shall check whether the timing requirements on the pins R and W are
fulfilled. (The assumption of this test is that the design will execute write and read accesses alternatingly.)
timing_check: process
begin
w_asserted := now;
wait until W = '1';
begin
W <= '1';
R <= '1';
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 9 ns;
wait for 10 ns;
-- write access
W <= '0', '1' after 7 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 4 ns; -- this is a violation we want to detect
wait for 10 ns;
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
wait;
As can be seen, the circuit does not meet the requirements. The second read pulse is too short. A simulation results
in the following output, however:
Closer examination of the timing_check process reveals that it contains an error causing it to verify only one write
and one read access, as it won't progress after the read unless a second reset pulse occurs. However the simulation
output is exactly as would be expected for a correctly functioning circuit.
This kind of error is not infrequent and can be avoided by using transaction logs. This is shown in the next section.
Below are the files which have been simulated in this section:
txt_util. hang.
vhd vhd
timing_check: process
begin
The transaction log contains an insufficient number of write and read cycles. The problem can be fixed
in the timing checker as shown:
loop
-- verify write access
wait until W = '0';
...
end loop;
In order to effectively work with transaction logs it's helpful to write them to files. Here is a way to do
this with the MTI simulator:
These logs should be kept for each test, so that in a later simulation run the current log can be
automatically compared with the golden log.
Below are the files which have been simulated in this section:
In principle the behavioural model will receive the same stimulus as the design and produce the same
output. However often the stimulus and response can be represented in a more abstract format. For
example if the actual design works on data blocks which are received one byte at a time in four clock
cycles, the behavioural model could just operate on a simple hex number.
Often behavioural models are used to generate expected (golden) log files. In this case the the
behavioural model operates on a stimulus file and creates a result file. The design is stimulated with the
same input file via a file reader (see Reading from Files) and will protocol it's response into a transaction
log (see Using Transaction Logs). The transaction log can then be compared with the expected results
from the behavioural model (e.g. with Unix' diff utility).
Below is a recommended directory structure which works well for the author. Many other structures are
of course just as useful. (Instead of module the name of the module should be used.)
Directory Contents
bin Scripts for running tests
doc Module documentation
Simulation directory,
sim contains the VHDL work library, simulator setup files,
current transaction log etc
Synthesis directory,
synth contains the work directory for intermediate files of the synthesis tool,
synthesis scripts, constraint files
Test Strategy
Testing should normally be done in a bottom-up fashion, which means that blocks will be simulated in
separate testbenches before they are integrated. This has the advantage that simulating smaller blocks
requires less computing resources, which means the simulation runs faster and it's easier to find errors.
Also since there is less code it's easier to identify the problem area. Of course there are limits to splitting
the testing effort into sub-blocks. The designer needs choose the right level of hierarchies by comparing
the effort to write additional testbenches against the effort to locate bugs in a more complex testbench.
Usually if several designers are involved there should be at least one testbench for each designer's
module and a testbench for the complete device.
There should be a testplan for the device which makes sure that every part of the design is exercised.
Each test should have a unique identifier (ideally the directory name in which the test files are kept) and
a description of the test.
If several designers work on the same device it's useful to have a bug log, a file that is kept in a central
location and has an entry for every bug which is found. Here is a possible format:
There are also tools which help to maintain these logs and (among other things) prevent that entries are
accidentally deleted.
Any small change in a minor file of the design could potentially cause the design to fail. It's therefore
important that all tests are run after the last change has been made to the design code (and before the
design is manufactured). To make this task feasible all tests should be automated and self-checking, so
that they can be run from a script instead of having to run every test manually and having to check every
log file line by line.
The End
I hope this course has been useful, I'm looking forward to your comments at: vc2@stefanVHDL.com
Thanks
© Stefan Doll
Below are the files which have been simulated in this section:
txt_util. finish.
vhd vhd
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
package txt_util is
-- file I/O
-----------
end txt_util;
begin
if b then
return "true";
else
return "false";
end if;
end str;
begin
num := abs_int;
end str;
begin
end str;
variable u: character;
begin
case c is
when 'a' => u := 'A';
when 'b' => u := 'B';
when 'c' => u := 'C';
when 'd' => u := 'D';
when 'e' => u := 'E';
when 'f' => u := 'F';
when 'g' => u := 'G';
when 'h' => u := 'H';
when 'i' => u := 'I';
return u;
end to_upper;
variable l: character;
begin
case c is
when 'A' => l := 'a';
when 'B' => l := 'b';
when 'C' => l := 'c';
when 'D' => l := 'd';
when 'E' => l := 'e';
when 'F' => l := 'f';
when 'G' => l := 'g';
when 'H' => l := 'h';
when 'I' => l := 'i';
return l;
end to_lower;
begin
end to_upper;
begin
end to_lower;
sl := '-';
when others =>
sl := 'X';
end case;
return sl;
end to_std_logic;
----------------
-- file I/O --
----------------
variable l: line;
variable c: character;
variable is_string: boolean;
begin
readline(in_file, l);
-- clear the contents of the result string
for i in res_string'range loop
res_string(i) := ' ';
end loop;
-- read all characters of the line, up to the length
-- of the results string
for i in res_string'range loop
read(l, c, is_string);
res_string(i) := c;
if not is_string then -- found end of line
exit;
end if;
end loop;
end str_read;
variable l: line;
begin
write(l, new_string);
writeline(out_file, l);
end print;
variable l: line;
begin
write(l, char);
writeline(out_file, l);
end print;
end str_write;
end txt_util;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity TB1 is
end TB1;
begin
test_seq: process
begin
x1 <= '0';
x2 <= '0';
x1 <= '1';
x2 <= '0';
x1 <= '0';
x2 <= '1';
x1 <= '1';
x2 <= '1';
x1 <= 'X';
x2 <= 'X';
x1 <= '1', '0' after 10 ns, '1' after 20 ns, '0' after 30 ns;
x2 <= '1', '0' after 20 ns;
end test;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use work.txt_util.all;
entity FILE_LOG is
generic (
log_file: string := "res.log"
);
port(
CLK : in std_logic;
RST : in std_logic;
x1 : in std_logic;
x2 : in std_logic_vector(7 downto 0)
);
end FILE_LOG;
begin
receive_data: process
variable l: line;
begin
end loop;
end log_to_file;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity STIM_GEN2 is
port(
RST: out std_logic;
CLK: out std_logic;
X1: out std_logic;
X2: out std_logic_vector(7 downto 0)
);
end STIM_GEN2;
begin
test_seq: process
begin
cnt := cnt + 1;
end test;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity tb_file_log is
end tb_file_log;
component file_log
generic (
log_file: string := "res.log"
);
port(
CLK : in std_logic;
RST : in std_logic;
x1 : in std_logic;
x2 : in std_logic_vector(7 downto 0)
);
end component;
component stim_gen2
port(
RST: out std_logic;
CLK: out std_logic;
X1: out std_logic;
X2: out std_logic_vector(7 downto 0)
);
end component;
begin
U_FILE_LOG: FILE_LOG
port map (
CLK => clk,
RST => rst,
x1 => x1,
x2 => x2
);
U_STIM_GEN2: STIM_GEN2
port map (
RST => rst,
CLK => clk,
X1 => x1,
X2 => x2
);
end structure;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use work.txt_util.all;
entity FILE_READ is
generic (
stim_file: string := "sim2.dat"
);
port(
CLK : in std_logic;
RST : in std_logic;
Y : out std_logic_vector(4 downto 0);
EOG : out std_logic
);
end FILE_READ;
-- I/O Dictionary
--
-- Inputs:
--
-- CLK: new cell needed
-- RST: reset signal, wait with reading till reset seq complete
--
-- Outputs:
--
-- Y: Output vector
-- EOG: End Of Generation, all lines have been read from the file
--
begin
receive_data: process
variable l: line;
variable s: string(y'range);
begin
end loop;
wait;
end read_from_file;
00010
00011
11100
1UXZW
HL111
11111
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity TB_FILE_READ is
end TB_FILE_READ;
component FILE_READ
generic (
stim_file: string := "sim.dat"
);
port(
CLK : in std_logic;
RST : in std_logic;
Y : out std_logic_vector(4 downto 0);
EOG : out std_logic
);
end component;
begin
rst <= '0', '1' after 40 ns, '0' after 100 ns;
clk <= not clk after 10 ns;
input_stim: FILE_READ
port map(
CLK => clk,
RST => rst,
Y => y,
EOG => eog
);
end test;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use std.textio.all;
use work.txt_util.all;
entity FILE_READ2 is
generic (
stim_file: string := "sim.dat"
);
port(
CLK : in std_logic;
RST : in std_logic;
Y : out std_logic_vector(4 downto 0);
EOG : out std_logic
);
end FILE_READ2;
-- I/O Dictionary
--
-- Inputs:
--
-- CLK: new cell needed
-- RST: reset signal, wait with reading till reset seq complete
--
-- Outputs:
--
-- Y: Output vector
-- EOG: End Of Generation, all lines have been read from the file
--
begin
receive_data: process
variable l: line;
variable s: string(1 to 80);
variable c: character;
variable in_string: boolean;
begin
for i in 1 to 5 loop
Y <= conv_std_logic_vector(i,5);
wait until CLK = '1';
end loop;
else
end if;
end loop;
wait;
end read_from_file;
00010
00011
#count
11100
1UXZW
HL111
11111
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity TB_FILE_READ2 is
end TB_FILE_READ2;
component FILE_READ2
generic (
stim_file: string := "sim2.dat"
);
port(
CLK : in std_logic;
RST : in std_logic;
Y : out std_logic_vector(4 downto 0);
EOG : out std_logic
);
end component;
begin
rst <= '0', '1' after 40 ns, '0' after 100 ns;
clk <= not clk after 10 ns;
input_stim: FILE_READ2
port map(
CLK => clk,
RST => rst,
Y => y,
EOG => eog
);
end test;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity SIMPLE_SRAM is
port(
A: in std_logic_vector(7 downto 0);
WE_L: in std_logic;
-- I/O Dictionary
--
-- A: Address bus
-- WE_L: Write Enable
-- D: Data bus
--
--
begin
dat_monitor: process
begin
test_prg: process
begin
start_cycle := now;
-- verify address
assert A = wadd
report "E@SIMPLE_SRAM: Address incorrect, expected "&
str(wadd)& " received "& str(A)
severity Error;
-- verify data
for i in wdat'range loop
if wdat(i) /= '-' and wdat(i) /= D(i) then
print("E@SIMPLE_SRAM: Write Data Invalid, written data = "& str(D)&
" expected data = "& str(wdat) );
exit;
end if;
end loop;
severity Error;
end write;
end read;
begin
-- Test Program
----------------
write("00000000", "11110000");
write("00000001", "00001111");
------------
-- End Test
end transactor;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use work.txt_util.all;
entity STIM_GEN is
port(
A: out std_logic_vector(7 downto 0);
WE_L: out std_logic;
D: out std_logic_vector(7 downto 0)
);
end STIM_GEN;
begin
test_seq: process
begin
-----------------------------
print(" ");
print(" one correct access");
A <= "00000000";
D <= "11110000";
-----------------------------
print(" ");
print(" wrong address and wrong data");
A <= "00000000";
D <= "11110000";
-----------------------------
print(" ");
print(" violate address setup and data hold time");
D <= "11110000";
A <= "00000000";
-----------------------------
print(" ");
print(" violate data setup and address hold time");
A <= "00000001";
D <= "00001111";
-----------------------------
print(" ");
print(" pulse width too short");
A <= "00000000";
D <= "11110000";
-----------------------------
print(" ");
print(" unstable address");
A <= "00000001";
D <= "00001111";
-----------------------------
print(" ");
print(" unstable data");
A <= "00000000";
D <= "11110000";
D <= "00000000";
-----------------------------
print(" ");
print(" one correct access");
A <= "00000001";
D <= "00001111";
wait;
end test;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity tb_simple is
end tb_simple;
component simple_sram
port(
A: in std_logic_vector(7 downto 0);
WE_L: in std_logic;
component stim_gen
port(
A: out std_logic_vector(7 downto 0);
WE_L: out std_logic;
D: out std_logic_vector(7 downto 0)
);
end component;
begin
U_SIMPLE_SRAM: SIMPLE_SRAM
port map (
A => a,
WE_L => we_l,
D => d
);
U_STIM_GEN: STIM_GEN
port map (
A => a,
WE_L => we_l,
D => d
);
end structure;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity SRAM2 is
generic(
mem_words: integer := 32
);
port(
RST: in std_logic; -- doesn't go to RAM, but is useful for testing
A: in std_logic_vector(7 downto 0);
WE_L: in std_logic;
RD_L: in std_logic;
-- I/O Dictionary
--
-- A: Address bus
-- WE_L: Write Enable
-- RD_L: Read Enable
-- D: Data bus
--
--
begin
wait_for_reset: process
begin
wait until RST = '1';
wait until RST = '0';
no_reset_yet <= false;
end process wait_for_reset;
dat_monitor: process
begin
test_proc: process
-- Internal Memory
type mem_add_type is array (integer range <>) of std_logic_vector(A'range);
type mem_dat_type is array (integer range <>) of std_logic_vector(D'range);
procedure write is
begin
start_cycle := now;
end write;
procedure read is
begin
start_cycle := now;
end read;
begin
assert (WE_L /= 'X' and WE_L /= 'Z' and WE_L /= 'U' and WE_L /= '-') or no_reset_yet
report "E@SRAM2: WE_L="& str(WE_L)& " invalid value"
severity Error;
assert (RD_L /= 'X' and RD_L /= 'Z' and RD_L /= 'U' and WE_L /= '-') or no_reset_yet
report "E@SRAM2: RD_L="& str(RD_L)& " invalid value"
severity Error;
end model;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.conv_integer;
use std.textio.all;
use work.txt_util.all;
entity MP is
port(
RST: in std_logic;
A: out std_logic_vector(7 downto 0);
WE_L: out std_logic;
RD_L: out std_logic;
D: inout std_logic_vector(7 downto 0)
);
end MP;
architecture test of MP is
begin
transactor: process
A <= wadd;
D <= wdat;
A <= radd;
D <= (others => 'Z');
begin
write("00001111","10101111");
wait for 5 ns;
read("00001111","10101111");
wait for 5 ns;
print(" ");
print("change the values of existing addresses");
print(" ");
write("00000011","10101111");
write("00000010","00000000");
print(" ");
print("and verify the changes");
print(" ");
read("00000011","10101111");
read("00000010","00000000");
print(" ");
print("do two weak writes");
print(" ");
write("00000100","10101010","weak");
write("00000101","01010101","weak");
print(" ");
print("do two weak reads");
print(" ");
read("00000100","10101010","weak");
read("00000101","01010101","weak");
wait;
end test;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity tb_simple2 is
end tb_simple2;
component sram2
generic(
mem_words: integer := 32
);
port(
RST: in std_logic; -- doesn't go to RAM, but is useful for testing
A: in std_logic_vector(7 downto 0);
WE_L: in std_logic;
RD_L: in std_logic;
component mp
port(
RST: in std_logic;
A: out std_logic_vector(7 downto 0);
WE_L: out std_logic;
RD_L: out std_logic;
D: inout std_logic_vector(7 downto 0)
);
end component;
begin
U_SIMPLE_SRAM: SRAM2
port map (
RST => rst,
A => a,
WE_L => we_l,
RD_L => rd_l,
D => d
);
U_MP: MP
port map (
RST => rst,
A => a,
WE_L => we_l,
RD_L => rd_l,
D => d
);
end structure;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity SIMPLE_MON is
end SIMPLE_MON;
begin
-- example stimulus
INT_L <= '1', '0' after 30 ns, '1' after 200 ns;
monitor: process(INT_L)
begin
end test;
library ieee;
use ieee.std_logic_1164.all;
entity TB_CLK_RST is
end TB_CLK_RST;
begin
end test;
#
# File insertion script
#
BEGIN {skip=0}
/--insert_file/ {
print "-- inserted file: " $2;
system("cat " $2);
print "-- -----------------------------------------------------------";
print "";
skip = 1}
// {
if(skip==0) {print $0};
skip = 0
}
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
entity TRANSACTOR is
port(
Y: out std_logic
);
end TRANSACTOR;
begin
--insert_file test_code.vhd
end test;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity HANG is
end HANG;
signal W: std_logic;
signal R: std_logic;
signal RST: std_logic;
begin
timing_check: process
begin
begin
W <= '1';
R <= '1';
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 9 ns;
wait for 10 ns;
-- write access
W <= '0', '1' after 7 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 4 ns; -- this is a violation we want to detect
wait for 10 ns;
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
wait;
end test;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity HANG2 is
end HANG2;
signal W: std_logic;
signal R: std_logic;
signal RST: std_logic;
begin
timing_check: process
begin
begin
W <= '1';
R <= '1';
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 9 ns;
wait for 10 ns;
-- write access
W <= '0', '1' after 7 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 4 ns; -- this is a violation we want to detect
wait for 10 ns;
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
wait;
end test;
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
use ieee.std_logic_arith.all;
use work.txt_util.all;
entity TLOOP is
end TLOOP;
signal W: std_logic;
signal R: std_logic;
signal RST: std_logic;
begin
timing_check: process
begin
loop
wait until W = '0';
w_asserted := now;
end loop;
begin
W <= '1';
R <= '1';
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 9 ns;
wait for 10 ns;
-- write access
W <= '0', '1' after 7 ns;
wait for 10 ns;
-- read access
R <= '0', '1' after 4 ns; -- this is a violation we want to detect
wait for 10 ns;
-- write access
W <= '0', '1' after 8 ns;
wait for 10 ns;
wait;
end test;
use std.textio.all;
use work.txt_util.all;
entity finish is
end finish;
begin
pr_proc: process
begin
print(" ");
print("I hope this course has been useful, I'm looking forward");
print("to your comments at: sdoll@intrinsix.com");
print(" ");
print("Thanks");
print(" ");
print("© Stefan Doll");
wait;
end process;
end text;
# Usage:
#
# perl tgen.pl > _output_file_
#
#
###########################################################################
print "00010\n";
print "00011\n";
print "11100\n";
for ($i=0;$i<10;$i++) {
print num2binary($i,5)."\n";
}
print "1UXZW\n";
print "HL111\n";
print "11111\n";
sub num2binary {
my($num) = @_;
my $binary = $num ? '' : '0'; # in case $num is zero
my $len = $_[1];
my $result;
while ($num) {
$binary .= $num & 1 ? 1 : 0; # do the LSB
$num >>= 1; # on to the next bit
}
return $result;
}