TestSimpleLed: Einführung in die Testumgebung
Das Testen der FPGAs hat einen universellen Code-Basierten Teil und einen IDE spezifischen Teil.
Das ein Teil IDE spezifisch ist, liegt daran, dass jede IDE ihren eigenen Simulator verwendet.
In diesen Tutorials verwende ich als IDE Vivado/Vitis. Wer also eine andere IDE verwendet kann sich u.U. in den Tutorials schlau machen, wie die anderen Simulatoren bedient werden.
Um die Schaltung zu testen, braucht man ein Testprogramm, die sogenannte Testbench. Die Testbench wird auch in VHDL programmiert, hat aber auch Funktionen, die man mit dem FPGA nicht direkt umsetzen kann. Z. B. "Verzögere ein Signal um 3 ns" oder "Warte 20 ms".
Wie ist die Testbench aufgebaut?
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
library std;
use std.env.all;
entity TestSimpleLed is
-- Port ( );
end TestSimpleLed;
architecture Behavioral of TestSimpleLed is
component SimpleLed
port (
Button : in std_logic;
Led : out std_logic
);
end component;
Anschließend wird die architecture deklariert in der die Testbench realisiert wird.
Zuerst wird die zu testende Komponente (uut: Unit under test) mittels component definiert. Da kann man die entity von SimpleLed kopieren und entsprechend anpassen. Das geht für alle entities/components.
--Inputs
signal Button : std_logic := '0'; -- Signal is high active
--Outputs
signal Led : std_logic;
--Test
signal TestCase : natural;
signal TestErr : std_logic := '0';
Jetzt muss man Signale definieren. Ganz wichtig dabei ist die Ein- und Ausgangssignale der uut zu definieren, damit die zu testende Komponente mit der Testbench stimuliert werden kann. Die Testbench wird dann später die Eingänge stimulieren und die Ausgänge mit dem zu erwartenden Verhalten abtesten.
In meinen Testbenches verwende ich immer noch die Signale TestCase und TestError. TestCase ist ein natural, also ein positiver integer. Mit diesen beiden Signalen, kann man bei längeren Tests erkennen, was gerade getestet wird, und ob schon ein Fehler aufgetreten ist.
begin
uut: SimpleLed port map (
Button => Button,
Led => Led
);
stim_proc: process
begin
Nach diesen ganzen Definitionen geht es los und zuerst wird die uut instantiiert. Man bindet also das programmierte Modul ein und verbindet es mit der Umwelt. Sinnigerweise verwende ich die gleichen Signalnamen wie die Portnamen.
In diesem Teil des Codegerüsts könnte man jetzt auch Funktionen definieren, welche im Testprozess verwendet werden. Diese gibt es hier aber nicht, deswegen geht es gleich mit dem Stimulationsprozess los.
wait for 100 ns;
report "=======================================================";
report "Only end of test and errors will be reported";
TestCase <= 1;
wait for 100 ns;
if (Led /= '0') then
TestErr <= '1';
report "Initial state failed";
end if;
Zum Beginn der Testbench wird 100 ns gewartet, damit sich alles einschwingt, eine ps würde wohl reichen, aber so hat man halt ein Intro mit TestCase 0, was im Simulator auch zu sehen ist.
Für den formalen Part werden Debug-Reports erzeugt, die man dann bei Vivado unter simulator.log sehen kann (siehe unten).
Schließlich geht es zum ersten Test. Da ja Button mit = :0 in der Testbench ein definierter Wert zugewiesen wurde, darf ich davon ausgehen, dass der Taster nicht gedrückt ist und die LED dementsprechend auch nicht angesteuert wird.
Falls das nicht zutreffen sollte wird das Fehlersignal TestErr gesetzt und eine entsprechende Meldung erzeugt.
Beim TestCase 1 wird auch 100 ns gewartet, damit es in der Simulation schön ausschaut und man erkennen kann, dass dieser TestCase auch abgearbeitet worden ist.
TestCase <= 2;
Button <= '1';
wait for 100 ns;
if (Led /= '1') then
TestErr <= '1';
report "Button pressed, but LED is off";
end if;
TestCase <= 3;
Button <= '0';
wait for 100 ns;
if (Led /= '0') then
TestErr <= '1';
report "Button released, but LED is on";
end if;
In den nächsten beiden Tests wird der Taster erst gedrückt und dann wieder losgelassen und die Reaktion überprüft.
Auch hier wird wieder 100 ns zum Einschwingen gelassen.
Eine Wartezeit von mindestens einer ps ist hier übrigens wichtig! Würde man nicht warten, wäre die Reaktion der LED noch nicht da, da ja das FPGA das Signal noch gar nicht gesehen hat.
TestCase <= 0;
wait for 100 ns;
TestErr <= not TestErr;
wait for 30 ns;
TestErr <= not TestErr;
wait for 100 ns;
if (TestErr = '0') then
report "Test completed successfully";
else
report "Test completed with error(s)";
end if;
report "=======================================================";
finish; -- stop simulation
Jetzt kommt noch etwas Kosmetik für das Ausgabefenster:
- TestCase wird wieder auf 0 gesetzt um zu zeigen dass nicht mehr getestet wird.
- nach weiteren 100 ns wird TestErr für 30 ns invertiert und dann noch mal 100 ns gewartet.
Das ist mein Daumen nach oben/unten am Schluss der Simulation. Falls kein Fehler aufgetreten ist, geht die Invertierung ja nach oben, ansonsten nach unten.
Letztendlich wird noch ausgegeben, ob der Test erfolgreich war oder nicht und der Schlussstrich gesetzt.
Die Simulation wird mit finish beendet. Der Simulator würde sonst geduldig weiter und weiter simulieren. Das Ende des Prozesses ist für ihn kein Grund mit der Simulation aufzuhören.
wait;
end process;
end Behavioral;
Jetzt wird nur noch die Testbench syntaktisch richtig abgeschlossen.
Während die Testbench läuft wird der folgende Report erzeugt:
Time resolution is 1 ps
Note: =======================================================
Time: 100 ns Iteration: 0 Process: /TestSimpleLed/stim_proc File: ...
Note: Only end of test and errors will be reported
Time: 100 ns Iteration: 0 Process: /TestSimpleLed/stim_proc File: ...
Note: Test completed successfully
Time: 630 ns Iteration: 0 Process: /TestSimpleLed/stim_proc File: ...
Note: =======================================================
Time: 630 ns Iteration: 0 Process: /TestSimpleLed/stim_proc File: ...
$finish called at time : 630 ns : File ... Line 77
Hier erkennt man, dass alles normal abgelaufen ist. Ansonsten wäre der Grund für den Fehler ausgegeben worden.
In der nächsten Lektion werden ich ein deutlich mächtigeres Tool für die Debug-Ausgabe bereit stellen. Das mit den reports wollte ich aber trotzdem erwähnt habe, weil es in jeder VHDL-Umgebung funktioniert.
Hier erkennt man jetzt sehr schön die Abfolge der einzelnen Tests. Während TestCase 2 wird der Taster gedrückt, was die LED ansteuert. Nach der Simulation kommt der Daumen nach oben und die Sache ist gegessen.
Weiter zu Vivado.