Testbench compile.do ausführen

Die Testbench wird am einfachsten über die Konsole über das Script gestartet. (Mit tab selbständig vervollständigen)

QuestaSim>   do <pfad>./compile_<projekt>.do
QuestaSim>   do ../simulation/script/compile_<projekt>.do


Script

Das Script compile.do , gibt den Befehl zum Einbinden der Bibliothek, zum Kompilieren der Dateien und startet schlussendlich die testbench.
Das Script liegt im Ordner Work . Alle Pfade bezieht sich vom Ort des Scripts aus.

# create work library
vlib work

# compile files
vcom -2008 -explicit -work work ../../source/counter.vhdl
vcom -2008 -explicit -work work <pfad>/componente_1.vhd
vcom -2008 -explicit -work work <pfad>/top_level.vhd

# compile testbench
vcom -2008 -explicit -work work <pfad>/testbench_top_level.vhd

# run the simulation
vsim -t 1ns -lib work work.testbench_entity

do ../scripts/wave_projekt.do

run 3000.0 ns

Die Reihenfolge der zu kompilierenden Dateien spielt eine Rolle:
– Zuerst die Bibliotheken und Packete
– Dann VHDL: tiefstes Level (spezfischste Komponente)
– Dann VHDL: höheres Level (Zusammenfügender Block)
– Am Schluss: Top-Level
– Testbench

Beim Kompilieren spielt die Version eine Rolle. Gebraucht man process(all), so wird mit der Version 2008 kompiliert. Für die std_textio-Bibliothek braucht man oft 2002, damit man keine Probleme der Doppeldefinition erhält.

Testbench Aufbau

In VHDL gehört die Simulation (schreiben einer Testbench) fix zur Entwicklung dazu. Die Testbench ist in VHDL geschrieben und liegt im Ordner vhdl.
Achtung: Nie in Quartus-Projekt einbinden.  Quartus kann waits und asserts nicht kompilieren.
Ausgeführt (kompiliert) wird die Testbench über die Konsole vom Simlationsprogramm (per Script):

Simulationsprogramm
Bei altera ist questasim die Vollversion des Simulationsprogramms.

work
Einbinden der Bibliothek

Ablage
Im Projektordner wird ein Ordner simulation angelegt. In diesen Ordner kommt – über das Einbinden der Bibliothek  work – der Unterordner Work.
In diesen legt man das Script zum Starten der Simulation compile.do .


Testbench Datei

– die entity hat keine Ports. Sie besteht nur aus Signalen
– die IOs jeder Componente werden an Signale gehängt
– die Testsignale (stimuli) werden in processen gesetzt
– Signale dürfen nur Signalen zugewiesen werden
– die Tests (Vergleichsergebnis von Signalen)
.  finden in den Prozessen statt. Die Processe haben keine Klammern

stimuli:   sind selbst entworfene Signalverläufe
waits:     bestimmen die zeitliche Abfolge unter den
.                  Signalen
reports: geben die Resultat aus
.                  (Konsole oder in File (textbasiertes Testing).
clk:         der clk muss simuliert (erzeugt) werden.
.                 Dies geschieht durch einen eigenen clk-prozess

clk_generator  : process
begin
.   wait for 1 * clk_halfp;
.       clock_sig <= '1';
.   wait for 1 * clk_halfp;
.       clock_sig <= '0';
end process;
entity ram_tb is
end ram_tb;


architecture struct of ram_tb is

---------------------------------------------------------------
-- component declaration
---------------------------------------------------------------
component ram
port(
.    address     : IN STD_LOGIC_VECTOR (15 DOWNTO 0);
.    clock       : IN STD_LOGIC  := '1';
.    data        : IN STD_LOGIC_VECTOR (7 DOWNTO 0);
.    wren        : IN STD_LOGIC ;
.    q           : OUT STD_LOGIC_VECTOR (7 DOWNTO 0)
);
end component;


---------------------------------------------------------------
-- signal instantiation
---------------------------------------------------------------

signal address_sig : STD_LOGIC_VECTOR (15 DOWNTO 0);
signal clock_sig   : STD_LOGIC  := '1';
signal data_sig    : STD_LOGIC_VECTOR (7 DOWNTO 0);
signal wren_sig    : STD_LOGIC ;
signal q_sig       : STD_LOGIC_VECTOR (7 DOWNTO 0);

signal clk_halfp   : time := 10 ns;
signal clk_period  : time := 20 ns;

-- eventuell constanten mit resultaten definieren

begin
---------------------------------------------------------------
-- component instantiation
---------------------------------------------------------------

inst_1 : ram
port map (
.   address  => address_sig,
.   clock    => clock_sig,
.   data     => data_sig,
.   wren     => wren_sig,
.   q        => q_sig
);

---------------------------------------------------------------
-- Set stimuli in process with timing
---------------------------------------------------------------
test_1: process(all)
.    -- Initialwerte setzen

.    -- Signale an Komponenten legen (Stimulation)

.   -- Warten

.   -- Signal von Ausgang Komponente abfangen

.   -- Vergleichen des Outputs mit Erwartung

.   -- Ausgabe Testresulat

end; -- test_1

end; -- architektur

Wie man die Tests startet, wird unter dem Eintrag Testbench ausführen erklärt. Eine professioneller Form des Testens basiert auf textbasierten Scripts.

Hier wird die Syntax der Testbench-Fehler (assert, report, severity,..) beschrieben.

VHDL: textbasierte Test: Aufbau File

File-Variablen definieren
Sie werden in dem Process definiert.

use std.textio.all;
use work.std_logic_textio.all;  // Kompilieren mit 2002

// other code bevor....

--------------------------------------------------------------------
-- testbased test 
--------------------------------------------------------------------
execute_file: process

    -------------------------------------
    -- File handling
    -------------------------------------
    file input_file: TEXT;       
    file output_file: TEXT;    
    
    variable line_in,line_out: Line; 
    variable good: boolean;   
    
    -- token structure: 
    -- type | note_1 | velc_1 | note_2 | velc_2 | numb_note | other
    VARIABLE token_type_n: string(1 to 5);    
    VARIABLE token_note_1: std_logic_vector(7 downto 0) :=(OTHERS => '0');
    VARIABLE token_velocity_1: std_logic_vector(7 downto 0) :=(OTHERS => '0');
    VARIABLE token_note_2: std_logic_vector(7 downto 0) :=(OTHERS => '0');
    VARIABLE token_nmbr_note_out: std_logic_vector(3 downto 0) :=(OTHERS=> '0');
    VARIABLE token_others: std_logic_vector(3 downto 0) :=(OTHERS=> '0');
    

Wie ein Inputfile aufgebaut werden soll, steht im Beitrag zum Inputfile.

 

File öffnen und Ende erkennen
– In VHDL werden Files zeilenweise eingelesen (siehe std_logic_textio)
– Eingelesen wird in einem Process

execute_file: process

-- zuerst File variablen (siehe oben)

BEGIN
    FILE_OPEN(input_file,"../simulation/script/input_midi.txt", READ_MODE);
    FILE_OPEN(output_file,"../simulation/script/result_midi.txt", WRITE_MODE);

 loop    
        ---------------------------------
        -- Check end of file
        --------------------------------
        if endfile(input_file) then  
                
                -- output console read line numbers      
                write(line_out, string'("Number of read lines from file:"));
                writeline(OUTPUT,line_out);                                  
                write(line_out, line_cnt);            
                writeline(OUTPUT,line_out);      
                assert false
                report "End of test"
                severity FAILURE;
            exit;
        end if;

Unmittelbar danach folgt (ebenfalls im loop)

Tokens einlesen

        -----------------------------------
        -- Read 10 token in (and check for error)
        ------------------------------------
        readline(input_file, line_in);     
        line_cnt <= line_cnt + 1;
        
        -- Skip empty lines
        next when line_in'length = 0;

        read(line_in, token_type_n, good);    
            assert good
            report "Error command (type) failed."
            severity ERROR;   
         
        hread(line_in, token_note_1, good);  
            assert good
            report "Error token (note_1) failed."
            severity ERROR;

Zuerst wird zeilenweise eingelsen (und die Zeilen gezählt.)
Dann teilt man die Wörter in Tokens ein. Je nach Tokentyp wird eine andere read-Funktion genommen.

Test durchführen
– Das Vergleichen der Werte einen getakteten Process
– Signale werden ausgelesen und mit Variablenwerten verglichen

test: process(all)
begin

 loop
    if rising_edge(clk) then
        -- Abarbeiten 1. Test ----------------------------------
        if (testtyp_n = 1) then

          -- Signale einlesen
          sig_wert1   <= puls_1;
          sig_wert2   <= data_slc;
          sig_result1 <= adder_out;

          wait for 2 * period;

          -- Test Wert
          if ( sig_wert1 /= wert_n) then

             -- Falls Fehler: Ausgabe in Zeile
             hwrite(line_out, wert_n); 
             write(line_out, string'(" nicht korrekt") ); 

          -- Test Ergebnis     
          elsif ( sig_result1 /= ergebnis_n) then

             -- Falls Fehler: Ausgabe in Zeile
             hwrite(line_out, ergebnis_n);
             write(line_out, string'("Ergebnis falsch"));

          -- Test fehlerfrei
          else
             zeilen_nr <= zeilen_nr + 1;
             hwrite(line_out, testtyp_n)
             write(line_out, string'(" fehlerfrei")); 
          end if;

       -- Abarbeiten 2. Test -----------------------------------
       elsif (testtyp_n = 2) then

            -- Signalwerte auslesen 
            
            -- Vergleich mit Variablen

            -- Ergebnis in Zeile schreiben

      -- Ende Abarbeiten Tests
      else
            assert false
            report "Unbekannter Testtyp"
            severity FAILURE;

     end if;    

     -- Zeile in File schreiben
     writeline(out_file, line_out);

 end loop; 

wait; 
end process;

Good Practice
Arbeitet man mit textbasiertem Debugging, so lässt man das Konsolendebugging (mit ASSERTS) weitmöglichst weg.

Textbasiertes Testing: Aufbau Inputfile

File mit Testwerten

1 00000001 00000001 00000002 0
1 FFFFFFFF FFFFFFFF 00000000 1
2 00000004 00000005 FFFFFFFE 1
2 FFFFFFFF FFFFFFFF 00000000 0

wr_ram 0001 00F1  11
wr_ram 0002 0015  11
wr_rm  0002 F632  10

rd_ram 0001 00F1  99
rd_ram 0002 F615  99

reset 00 00 00 00 00 00 00 00 00
check 00 00 00 00 00 00 00 00 00
singl 13 01 00 00 00 00 00 00 00
check 13 00 00 00 00 00 00 00 00

Die Daten werden entweder als string  mit der Funktion read(<von>,<zu>)  oder als Hex-Zahl hread(<von>,<zu>)  eingelesen (vgl. std_logic_textio.vhd).

Richtige Zahlenwerte eintragen
Weil die Zahlen als Hex interpretiert werden, entspricht eine 0  4 Bits. Und die Zahl 10  entspricht dem dezimalen Wert 15.

Aus diesem Grund enstehen folgende Anzahl Bits:
00  im File    ->      std_logic_vector(7 downto 0)  beim Einlesen in der Testbench
0   im File    ->     std_logic_vector(3 downto 0)
7  im File     ->     std_logic_vector(3 downto 0)
0F5  im File  ->     std_logic_vector(11 downto 0)


Aufbau der Token
Die Token können alle vom selben Datentyp sein und über alle Testtyps die gleiche Struktur beinhalten. Das vereinfacht ihre Verarbeitung.

<Testtyp>  <Wert1>   <Wert2>    <Ergebnis_Addition> <Ueberlaufbit>
1          00000001  00000001   00000002             0

Im zweiten Beispiel hängt die Funktion der Token von dem Testtyp ab.

<Testtyp>  <adress>  <value>   <byte enable>
wr_ram     0001      00F1      11
<Testtyp>  <address>  <result>  <byte enable>
rd_ram     0001       00F1      01

Tipp: Fixe Tokenstruktur mit Auslassungen

singl 14 01 00 00 00 00 00 00 00   // verwerfen
polyp 13 01 14 01 15 01 14 00 02

Zum Decodieren wird es schwer, wenn unterschiedliche Verarbeitungs-Strukturen je nach Testtyp aufgebaut werden müssen.

Zu empfehlen ist, dass alle Test-Werte in einer Linie stehen und beim Verarbeiten gewisse Token verworfen werden.

Umsetzung in VHDL