-------------------------------------------------------------------------------- -- Project Name: Generic_Test_bench -- Written By: Trescott D. Jensen -- Create Date: 14:43:34 11/17/2011 -- Notes: -- Written for the benefit of Fon Brown's EE 3610 Class. -- You can play with this module to learn more about working with files. -- If you want to use it for a project of yours, let ISE generate the basic -- testbench for your project and then follow these steps to modify it. -- * Step 1: copy and paste the three sections indicated section 3 -- replaces the default test process. -- * Step 2: in section 2, modify test_data_holder to include all the -- inputs and outputs as well as all expected results for the -- outputs. Include any other values to be read/writen to file -- * Step 3: adjust wrong_indicator to hold as many or few separete -- indicators as is needed. -- * Step 4: in section 3, update the header literal string to match -- the data that you will be writing. -- * Step 5: replace the read/hread blocks with one for each field of -- data that will be in your input file, make sure they're -- in the right order and that you use the right read function -- for that type of data. Keep the comments copying code. -- make sure to update the field name in the report text. -- * Step 6: update the "apply test signals" and the "save results" blocks -- to work with your signals. **CAREFULL** -- Mistakes here will be hard to find later! -- * Step 7: update the "compare results" block using the examples use -- what works best for your project. -- * Step 8: update the "Write results" and "flag errors" blocks to match -- the changes you made. Keep in mind more info is better. -- Note that the number at the end of the write functions is -- the fixed size of the field, adjust this to fit your data -- and include space to separate them. -- i.e. 2 characters of data gets 3 or 4 not just 2. -- * Step 9: Write test data for your project, include a header in your file -- * Step 10: RUN YOUR TEST. And begain the troubleshooting process! -------------------------------------------------------------------------------- LIBRARY ieee; USE ieee.std_logic_1164.ALL; ---------------------- Section 1 Cut Here -----------------------------------------\ USE ieee.numeric_std.ALL; -- this is commented out by default but we need it. USE ieee.std_logic_textio.all; -- for some file reading functions USE std.textio.ALL; -- for other file reading functions ---------------------- Section 1 Cut Here -----------------------------------------/ ENTITY generic_test_bench IS END generic_test_bench; ARCHITECTURE behavior OF generic_test_bench IS -- Component Declaration for the Unit Under Test (UUT) COMPONENT gen PORT( clk : IN std_logic; inputs : IN std_logic_vector(15 downto 0); outputs : OUT std_logic_vector(15 downto 0) ); END COMPONENT; --Inputs/outputs signal clk : std_logic := '0'; signal inputs : std_logic_vector(15 downto 0) := (others => '0'); signal outputs : std_logic_vector(15 downto 0) := (others => '0'); -- Clock period definitions constant clk_period : time := 10 ns; ------------------- Section 2 Cut Here -----------------------------------------\ -- testbench types type test_data_holder is record inputs : std_logic_vector(15 downto 0); ------------------------ outputs : std_logic_vector(15 downto 0); -- data from/to files -- expected : std_logic_vector(15 downto 0); ------------------------ count : integer range 0 to 2000; -- counts tests performed wrong_count : integer range 0 to 2000; -- counts total error wrong_indicator : string(2 downto 1); -- indicats a test error (current) end record; type comments is record content: string (200 downto 1); -- holds comments from input file len: integer range 0 to 200; -- indicates length of valid data end record; type files is record out_line: line; -- handle for current line in file in_line: line; -- handle for current line in file line_number: integer range 1 to 2000; -- current line No. alignment: side; -- field alinement bad : boolean; -- read failure feedback holder end record; -- testbench variables -- these are global in case more then one process is needed to perform the tests shared variable test : test_data_holder; -- accual variable for the record above shared variable comment: comments; -- accual variable for the record above shared variable f: files; -- accual variable for the record above file f_out: text; -- handle for file file f_in: text; -- handle for file shared variable Done: boolean := false; ------------------- Section 2 Cut Here -----------------------------------------/ BEGIN -- Instantiate the Unit Under Test (UUT) uut: gen PORT MAP ( clk => clk, inputs => inputs, outputs => outputs ); -- Clock process definitions clk_process :process begin clk <= '0'; wait for clk_period/2; clk <= '1'; wait for clk_period/2; end process; ------------------- Section 3 Cut Here -----------------------------------------\ -- testbench - loads test data from file, applies data, waits for result, -- compares to expected results, counts errors, -- writes results to file testbench: process -- Runs only once then waits forever begin -- initialized variables test.count :=0; -- test count will increment at the begining of first test f.line_number := 1; -- We normally call the first line 1 not 0 so start at 1 f.alignment := left; -- sets the alinment for the write functions -- open the input and output files -- the file names are diclared here file_open(f_in, "test_data.txt", read_mode); file_open(f_out, "test_results.txt", write_mode); -- handle the headders readline (f_in,f.in_line); -- read the header line but ignore it write(f.out_line, -- create header on the first line of the output file. "Inputs Expected Accual Exp(bin) Acc(bin) Er COMMENTS"); writeline(f_out, f.out_line);-- write the header line to the file. -- wait until the clock is low so we can set the input data -- we want to do everything while the clock is low wait until clk='0'; -- loop through each test -- each loop is one line of the input file each line of the input file is one test -- i.e. one loop = one test while not Done loop -- maybe redundent with the next two lines but doesn't hurt Done := endfile(f_in); -- we're done when we get to the end of the file exit when Done; -- if we're done exit the testing loop -- load the test data from the file into the line variable f.line_number := f.line_number + 1; -- keep track of the current line for error reporting readline (f_in, f.in_line); if (f.in_line'length = 0) then -- when line is blank writeline(f_out, f.out_line); -- copy the blank line to the output file next; -- then skip the current line/loop/test end if; -- count the tests test.count := test.count + 1; -- load the test data from the line variable -- reads one field at a time skipping white space so your data can still be -- human readable, i.e. spaces and tabs hread (f.in_line, test.inputs, f.bad); -- hread interprets the text as hexadecimal assert f.bad -- reports read error, this is great for finding the error in data file. report "Text I/O read error of inputs field on line " --Note: the field is named here & integer'image(f.line_number) & " of input file." -- give line number of error. severity ERROR; read (f.in_line, test.Expected, f.bad); -- read interprets the text as binary/std_logic assert f.bad -- reports read error on the simulation console report "Text I/O read error of Expected field on line " --Note: the field is named here & integer'image(f.line_number) & " of input file." -- give line number of error. severity ERROR; -- copy comments from input file to variable to be writen to the output file later -- this is really handy when reading the results, but also captures the white space -- in this case read is using a string variable so it will interpret the text as text -- string variables are not variable length, but we need to match the length of the data -- so we control how much of the string variable we use Comment.len := f.in_line'length; read (f.in_line, Comment.content(Comment.len downto 1), f.bad);-- bad is true when it fails assert f.bad -- reports read error on the simulation console report "Text I/O read error of Comments field on line " --Note: the field is named here & integer'image(f.line_number) & " of input file." -- give line number of error. severity ERROR; -- apply test signals inputs <= test.inputs; -- wait for results -- the data we put in is valid at the rising clk edge, but the data out isn't valid -- until the next rising clk edge, but sense this is a simulation we can cheat by -- reading it at the falling clk edge of the same cycle, becuae the combinational -- logic in the simulation will likely only take a few deltas of time to complete. -- if this doesn't work for you, split this process into two processes, one setting -- the inputs while the other is checking the outputs at the same time. this will -- require more work to get it right. wait until clk='1'; wait until clk='0'; -- save results test.outputs := outputs; -- compare results to expected (Ver. A) ---\ -- = will look for an exact match, '-' must be compared to another '-' | -- to be true, i.e. '-' does not match '1' or '0', but it does handle | -- 'U', 'Z', and 'X', i.e. 'U' = 'U' is true | if test.Expected = test.outputs then -- | test.wrong_indicator(1 downto 1) := " "; -- | else -- | test.wrong_indicator(1 downto 1) := "!"; -- what ever works for you | end if; -- ___/ -- compare results to expected (Ver. B) ---\ -- std_match will treat don't cares ('-') the way that you would expect | -- i.e. std_match('-','1') is true, but it doesn't handle 'Z', 'X', 'U' | -- i.e. std_match('U','U') is false. | if std_match(test.Expected, test.outputs) then -- | test.wrong_indicator(2 downto 2) := " "; -- | else -- | test.wrong_indicator(2 downto 2) := "*"; -- what ever works for you | end if; -- ___/ -- Write results to output line -- write will write the data as binary, -- hwrite will write the data as hexadecamal hwrite(f.out_line, test.inputs, f.alignment, 8); hwrite(f.out_line, test.Expected, f.alignment, 10); -- hex is easy to read hwrite(f.out_line, test.outputs, f.alignment, 8); -- but doesn't tell all write(f.out_line, test.outputs, f.alignment, 18); -- binary tells all write(f.out_line, test.Expected, f.alignment, 18); -- but isn't easy to read -- flag test errors (if any) on current line write(f.out_line, test.wrong_indicator, f.alignment, 4); -- quickly find the errors -- copy the comments from the variable to the end of the output line write(f.out_line, Comment.content(Comment.len downto 1), f.alignment, Comment.len); -- write the output line to the output file writeline(f_out, f.out_line); report "Test # " -- report the current test & integer'image(test.count) & " Done." -- give test number. severity NOTE; -- end of test cycle, loop back to next test or if Done move on end loop; -- We're Done, so close the files file_close(f_in); file_close(f_out); -- wait forever -- this is needed because the process acts like a loop, -- when it finishes it runs again, running/looping forever. wait; end process; ------------------- Section 3 Cut Here -----------------------------------------/ END;