8a

VHDL models and test benches

prepared by P. Bakowski


Introduction

The testbench (TB) is an environment to simulate and verify the operation of the Unit Under Test (UUT). In our case, the test bench environment is a program-algorithm written in VHDL as the hardware model itself. The advantage of such an approach is that there is no need to learn a special simulation tool or test language.

The objectives of a TB are:

The GU (generation unit) is written as a separate entity at the top of the testbench; it contains no input or output ports.

Content: embedded test, test vector generation, clock signals, dense test vectors, random test vectors, array constants for test vectors, using files, complete test bench, exercises


Embedded test

In some cases the test may be applied inside the UUT. To do this, the assertion statements are inserted into the tested model. Recall that the assertion statement checks the value of a boolean expression, and if it is not true it displays a message. The severity level depends on the importance of the problem detected during simulation.
 
severity level -- -------meaning-----------------
note 

warning 

error 

failure

simple message about the verified condition 

potential problem 

condition that will cause an error  

condition that will cause a disastrous error

 

Test vector generation

With VHDL, there are three ways to generate test vectors:

Test vector generation by an algorithm

The test vectors may be generated by different kinds of functions and/or algorithms: Simple assignments - clock signals
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity clocks is
port(clk1,clk2,clk3 :out std_logic);
end clocks;
architecture first of clocks is
constant freq1: integer:= 100; -- fast clock frequency
constant freq2: integer:= 10; -- slow clock frequency
constant del3: time:= 10 ns;
signal tclk1,tclk2,tclk3: std_logic:='0';
begin
tclk1 <= not tclk1 after (1000/freq1) * 1 ns;
tclk2 <= not tclk2 after (1000/freq2) * 1 ns;
tclk3 <= not tclk3 after del3;
clk1 <= tclk1;
clk2 <= tclk2;
clk3 <= tclk3;
end first;
 
Asymmetrical repetitive clock waveforms
 
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity clocks2 is
port(clk :out std_logic);
end clocks2;
architecture first of clocks2 is
constant del1: time:= 10 ns; -- high
constant del1: time:= 20 ns; -- low
begin
process
begin
clk <= transport '1' after del1, '0' after del2;
wait for del1+del2;
end process;
end first;
 
 

Generating dense test vectors

The use of loop statement allows the designer to generate any complex and periodic test sequence. For example the following entity generates a 16-bit Gray-code pattern. The provided test vectors have only one bit changes between the adjacent values in the sequence. This may be very usefull for an extensive test coverage.

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity graytest is
port(clk: in std_logic;grayvect :out unsigned( 7 downto 0));
end graytest;
architecture first of graytest is
constant holdstep: integer:= 4;
begin
process
begin
grayvect <= (others=>'0');
for i in 0 to 256*holdstep*2-1 loop
grayvect <= to_unsigned(i,8) xor shift_right(to_unsigned(i,8),1);
for j in 0 to holdstep loop
wait until rising_edge(clk);
end loop;
wait until falling_edge(clk);
end loop;
end process;
end first;
 

Generating random test vectors

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity randtest is
port(clk: in std_logic;at :out unsigned( 9 downto 0));
end randtest;
architecture first of randtest is
signal a: unsigned(9 downto 0):= "0000000000";
constant a_tap: unsigned(9 downto 0):= "1000000100";
constant wa: integer:= 10;
begin
process(clk)
variable a: unsigned(9 downto 0):= "0000000000";
variable a_feed: std_logic;
begin
if rising_edge(clk) then
a_feed:='0';
for i in 0 to wa-2 loop
a_feed:= a_feed nor a(i);
end loop;
a_feed:= a_feed xor a(wa-1);
for i in wa-1 downto 1 loop
if (a_tap(i-1)='1') then
a(i):= a(i-1) xor a_feed;
else
a(i):= a(i-1);
end if;
end loop;
a(0) := a_feed;
at <= a;
end if;
end process;
end first;


Using array constants for test vectors

The test vectors may be stored as constants in an array. This method is convenient for small number of vectors. More numerous test vectors should be stored in a separate file.
 
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity arraytest is
generic(buswidth:positive:= 6);
port(fbus :out unsigned( buswidth-1 downto 0));
end arraytest;
architecture first of arraytest is
type freq is array(0 to 7) of integer;
constant busfreq: freq:=(10,33,2,5,20,23,30,14);
constant bus_delay: time:=40 ns;
begin
process
variable bus_v: unsigned(buswidth-1 downto 0);
begin
for i in 0 to 7 loop
fbus <= to_unsigned(busfreq(i),buswidth);
wait for bus_delay;
end loop;
end process;
end first;
 


Using files to store the test vectors generated by VHDL model

The standard package textio includes routines to read and write files containing data or text. The user can generate the test vectors and write them into the files containing signal names and logic states. This type of test vector coding requires the knowledge of how to handle various type conversions.
The following example shows how to create a file named simvect.sv for use with a gate level simulator.
The file needs: Example of the file content: The binary values written into the file must be characters. That is why we need a function to convert the standard logic values into characters.
It may look as follows:
function to_char(x:std_logic) return character is
begin
case x is
when 'L' | '0' => return '0';
when 'H' | '1' => return '1';
when 'Z' => return 'Z';
when others => return 'X';
end case;
end ;
 
 
library IEEE;
use IEEE.std_logic_1164.all;
use std.textio.all;
entity filetest is
port(clk,rst:in std_logic);
end filetest;
architecture first of filetest is
begin
wr_file: process -- the process waits the events on clk and rst signals
function to_char(x:std_logic) return character is
begin
case x is
when 'L' | '0' => return '0';
when 'H' | '1' => return '1';
when 'Z' => return 'Z';
when others => return 'X';
end case;
end ;
variable init: boolean:= false;
file outfile : text is out "simvect.sv"; -- text is subtype, out is mode
variable ptr : line;
variable now_i: integer;
constant s_clk : string:="clk";
constant s_rst : string:="rst";
begin
if init=false then -- write header
write(ptr,2); -- write the number 2
writeline(outfile,ptr);
write(ptr,s_clk);
writeline(outfile,ptr);
write(ptr,s_rst);
writeline(outfile,ptr);
init := true;
end if;
wait until clk'event or rst'event;
now_i := now/1 ns; -- get the current time
write(ptr,now_i,FIELD=>12); -- put the time value
write(ptr,' '); write(ptr,' ');
write(ptr,' '); write(ptr,' ');
write(ptr,to_char(clk));
write(ptr,to_char(rst));
writeline(outfile,ptr); -- write the line ptr into the file
end process;
end first;
 

Using files to read the stored test vectors

The traditional way of testing models is the use of test vectors stored in the special files. Such files may be generated by some VHDL independent tools or by a VHDL model as presented above.
The following example shows how to open and read a file named inpvect.sv for use with a simulated unit.
 
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;
entity testfile is
port(rd,wr:out std_logic; abus: out unsigned(7 downto 0); dbus: out unsigned (15 downto 0));
end testfile;
architecture first of testfile is
begin
rd_file: process
function to_std(x:bit) return std_logic is
begin
case x is
when '0' => return '0';
when '1' => return '1';
end case;
end ;
function to_unsigned(x:bit_vector) return unsigned is
variable result:unsigned (x'range);
begin
for i in x'range loop
case x(i) is
when '0' => result(i):='0';
when '1' => result(i):='1';
end case;
end loop;
return result;
end ;
variable last_time:integer:=0;
variable now_int:integer;
file infile : text is in "inpvect.sv";
variable ptr : line;
variable abus_v:bit_vector(7 downto 0):="00000000";
variable dbus_v:bit_vector(15 downto 0):="0000000000000000";
variable rd_v:bit:='0';
variable wr_v:bit:='0';
begin
while not(endfile(infile)) loop
readline(infile,ptr);
read(ptr,now_int); -- read time value
read(ptr,abus_v); -- read abus variable value
read(ptr,dbus_v); -- read dbus variable value
read(ptr,rd_v); -- read rd variable value
read(ptr,wr_v); -- read rd variable value
wait for(now_int - last_time) * 1 ns;
abus <= to_unsigned(abus_v);
dbus <= to_unsigned(dbus_v);
rd <= to_std(rd_v);
wr <= to_std(wr_v);
last_time := now/1 ns;
end loop;
wait;
end process;
end first;
 

The input vector test file inpvect.sv:

and the results of simulation with the input vectors read from the inpvect.sv file:


A complete test bench example

The following model is a test bench of the Greatest Common Divisor algorithm.The algorithm finds the Greatest Common Divisor according to the following scheme:

The algorithm operates by continually subtracting the smaller of the two numbers. If a becomes smaller than b, it swaps the values and continues until a or b equals zero. The VHDL model integrates two files one for the input test and reference vectors and one for the verification results. The input file is called gcd_test.sv; the output file is called gcd_res.sv.

For example the gcd_test.sv file contains:

After the simulation run that detected no errors; the output file gcd_res.sv content  is: The VHDL model code is;
library IEEE;
use IEEE.std_logic_1164.all;
use std.textio.all;
entity testbench is -- the testbench for GCD algorithm
end testbench;
architecture first of testbench is
file testvector: text is in "gcd_test.sv";
file resultvector: text is out "gcd_res.sv";
begin
process
variable a,b,a_bis,b_bis,swap,x,x_ref: integer range 0 to 1024;
variable testline: line;
variable bufline: line;
variable pass_fail: bit:='1';
begin
-- reading test vectors
while not endfile(testvector) loop -- test and result data
readline(testvector, testline);
read(testline,a);
read(testline,b);
read(testline,x_ref);
-- algorithm
a_bis :=a;
b_bis :=b;
if (a_bis /=0 and b_bis /=0) then
while (b_bis/=0) loop
while (a_bis >= b_bis) loop
a_bis := a_bis - b_bis;
end loop;
swap:= a_bis;
a_bis := b_bis;
b_bis := swap;
end loop;
else
a_bis := 0;
end if;
x := a_bis;
-- verification
if(x /= x_ref) then
pass_fail :='0';
write(bufline,string'("error: a_bis="));
write(bufline,a);
write(bufline,string'("b_bis="));
write(bufline,b);
write(bufline,string'("x="));
write(bufline,x);
write(bufline,string'("x_ref="));
write(bufline,x_ref);
writeline(resultvector,bufline);
end if;
end loop;
if(pass_fail ='1') then
write(bufline,string'("algorithm verified"));
writeline(resultvector,bufline);
end if;
wait;
end process;
end first;

Exercises