Erste Schritte mit VUnit
VUnit ist eines der beliebtesten Open-Source-VHDL-Verifizierungsframeworks, die heute verfügbar sind. Es kombiniert einen Python-Testsuite-Runner mit einer dedizierten VHDL-Bibliothek, um Ihre Testbenches zu automatisieren.
Um Ihnen dieses kostenlose VUnit-Tutorial zu geben, engagiert VHDLwhiz Ahmadmunthar Zaklouta, der hinter dem Rest dieses Artikels steht, einschließlich des einfachen VUnit-Beispielprojekts, das Sie herunterladen und auf Ihrem Computer ausführen können.
Lass uns Ahmad das Wort geben!
Dieses Tutorial soll die Verwendung des VUnit-Frameworks im Verifizierungsprozess Ihres Designs demonstrieren. Es führt Sie durch den Prozess der Einrichtung von VUnit, der Erstellung der VUnit-Testbench, der Verwendung der VUnit-Prüfbibliothek und der Ausführung von VUnit-Tests in ModelSim. Außerdem werden einige Überprüfungstechniken demonstriert.
Übersicht
Dieser Artikel enthält mehrere Screenshots. Klicken Sie auf die Bilder, um sie zu vergrößern!
Verwenden Sie die Seitenleiste, um durch die Gliederung zu navigieren für dieses Tutorial oder scrollen Sie nach unten und klicken Sie auf die Pop-up-Navigationsschaltfläche in der oberen rechten Ecke, wenn Sie ein Mobilgerät verwenden.
Anforderungen
Dieses Tutorial geht davon aus, dass diese Software auf einem Windows-Rechner installiert ist:
- Intel ModelSim
- In diesem Artikel erfahren Sie, wie Sie ModelSim kostenlos installieren.
- ModelSim sollte sich in Ihrem PATH befinden.
- Python 3.6 oder höher.
- Python herunterladen
- Python sollte sich in Ihrem PATH befinden.
- GIT (optional).
- GIT herunterladen
- Windows-Terminal (optional)
- Windows-Terminal herunterladen
Außerdem werden grundlegende VHDL-Kenntnisse und ModelSim-Kenntnisse vorausgesetzt.
Installieren
- VUnit wird abgerufen:
- Wenn Sie GIT haben, können Sie es von GitHub auf Ihr C-Laufwerk klonen:
git clone --recurse-submodules https://github.com/VUnit/vunit.git
- Andernfalls können Sie es als ZIP-Datei von GitHub herunterladen und auf Ihr C-Laufwerk extrahieren:
- VUnit herunterladen.
- Andernfalls können Sie es als ZIP-Datei von GitHub herunterladen und auf Ihr C-Laufwerk extrahieren:
- Installieren von VUnit:
- Öffnen Sie ein Terminal und navigieren Sie zu
C:\vunit
und geben Sie den folgenden Befehl ein:
- Öffnen Sie ein Terminal und navigieren Sie zu
python setup.py install
- VUnit wird konfiguriert:
- Fügen Sie Umgebungsvariablen wie unten beschrieben zu Ihrem System hinzu.
VUNIT_MODELSIM_PATH
:Pfad zu ModelSim auf Ihrem Rechner.VUNIT_SIMULATOR
:ModelSim
Beispielprojekt herunterladen
Sie können das Beispielprojekt und den VHDL-Code mit dem unten stehenden Formular herunterladen.
Extrahieren Sie die ZIP-Datei nach C:\vunit_tutorial .
Einführung
VUnit ist ein Testframework für HDL, das den Verifizierungsprozess vereinfacht, indem es einen testgesteuerten Workflow „früh und oft testen“ und eine Toolbox für die Automatisierung und Verwaltung von Testläufen bereitstellt. Es ist ein fortschrittliches Framework mit umfangreichen, reichhaltigen Funktionen, aber dennoch einfach zu bedienen und anzupassen. Es ist vollständig Open-Source und kann problemlos in herkömmliche Testmethoden integriert werden.
VUnit besteht aus zwei Hauptkomponenten:
- Python-Bibliothek: bietet Tools, die bei der Testautomatisierung, -verwaltung und -konfiguration helfen.
- VHDL-Bibliothek: eine Reihe von Bibliotheken, die bei allgemeinen Überprüfungsaufgaben helfen.
Der VHDL-Teil besteht aus sechs Bibliotheken, wie im folgenden Diagramm dargestellt. Dieses Tutorial verwendet die Logging- und Checking-Bibliotheken.
Design im Test
Das in diesem Tutorial verwendete Design mit dem Namen motor_start
, implementiert einen Startvorgang für einen bestimmten Motor und treibt 3 LEDs an, die den Status des Motors darstellen.
Die Schnittstelle besteht aus einem Eingabedatensatz motor_start_in
mit 3 Elementen (3 Schalter als Eingänge) und einem Ausgangsdatensatz motor_start_out
mit 3 Elementen (3 LEDs ROT, GELB, GRÜN als Ausgänge).
Einige Motoren müssen am Anfang initialisiert werden, bevor Sie sie verwenden können. Unser Motorstartverfahren besteht aus drei Schritten:
- Lade Konfiguration
- Kalibrierung
- Rotation
Startsequenz
Hier folgen die Startsequenzen des Motors und die Bedeutung der LED-Anzeigen.
- RED_LED steht für Ladekonfiguration .
- Schalter_1 einschalten.
- Die RED_LED beginnt 5 Mal zu blinken.
- Warten Sie, bis die RED_LED aufhört zu blinken und konstant leuchtet.
- GELBE_LED steht für Kalibrierung laden .
- Jetzt können Sie Schalter_2 einschalten.
- Wenn Schalter_2 eingeschaltet wird, beginnt YELLOW_LED nach 5 Taktzyklen konstant zu leuchten und dauert 10 Taktzyklen. Und danach erlöschen YELLOW_LED und RED_LED.
- GRÜNE_LED zeigt an, dass sich der Motor dreht.
- Nun ist der Motor einsatzbereit. Jedes Mal, wenn Schalter_3 eingeschaltet wird, beginnt die GRÜNE_LED konstant zu leuchten, bis Schalter_3 ausgeschaltet wird.
- Jeder Verstoß gegen diese Sequenz führt dazu, dass alle 3 LEDs konstant leuchten, bis alle Schalter ausgeschaltet werden, und die Sequenz kann erneut gestartet werden.>
Testbench-Entwicklung
Dieser Teil des Tutorials besteht aus den folgenden Unterabschnitten:
- Einrichten des Python-Laufskripts
- Einrichten des VUnit-Skeletts
- Aufbau der Testbench
Einrichten des Python-Laufskripts run.py
Jedes VUnit-Projekt benötigt ein Python-Skript der obersten Ebene run.py
das als Einstiegspunkt in das Projekt fungiert und alle VHDL-Design-, Testbench- und Bibliotheksquellen angibt.
Diese Datei ist normalerweise im Projektverzeichnis vorhanden. Der in diesem Tutorial verwendete Verzeichnisbaum sieht wie folgt aus:
Im run.py
-Datei müssen wir drei Dinge tun:
1 – Rufen Sie den Pfad ab, in dem diese Datei vorhanden ist, und geben Sie den Pfad für Design- und Testbench-Dateien an.
# ROOT ROOT = Path(__file__).resolve().parent # Sources path for DUT DUT_PATH = ROOT / "design" # Sources path for TB TEST_PATH = ROOT / "testbench"
2 – Erstellen Sie die VUnit-Instanz.
Dadurch wird eine VUnit-Instanz erstellt und einer Variablen mit dem Namen VU
zugewiesen . Dann können wir VU
verwenden um Bibliotheken und verschiedene Aufgaben zu erstellen.
# create VUnit instance VU = VUnit.from_argv() VU.enable_location_preprocessing()
3 – Erstellen Sie Bibliotheken und fügen Sie ihnen VHDL-Quellen hinzu.
Ich trenne gerne den Design-Teil vom Testbench-Teil. Daher erstellen wir zwei Bibliotheken:design_lib
und tb_lib
.
# create design library design_lib = VU.add_library("design_lib") # add design source files to design_lib design_lib.add_source_files([DUT_PATH / "*.vhdl"]) # create testbench library tb_lib = VU.add_library("tb_lib") # add testbench source files to tb_lib tb_lib.add_source_files([TEST_PATH / "*.vhdl"])
Der Rest der Datei ist eine Konfiguration für ModelSim, um den wave.do
zu verwenden Datei, falls vorhanden.
Hinweis: hier verwende ich den *.vhdl
Verlängerung. Sie müssen es möglicherweise ändern, wenn Sie *.vhd
verwenden .
Wenn Ihnen diese Arbeitsstruktur gefällt, brauchen Sie diese Datei überhaupt nicht zu ändern. Wenn Sie ein neues Projekt starten, kopieren Sie es einfach in Ihr Projektverzeichnis. Wenn Sie jedoch eine andere Verzeichnisstruktur bevorzugen, müssen Sie die Pfade an Ihre Arbeitsstruktur anpassen.
Wann immer wir diese Datei verwenden, scannt VUnit jetzt automatisch nach VUnit-Testbenches in Ihrem Projekt, bestimmt die Kompilierungsreihenfolge, erstellt die Bibliotheken und kompiliert Quellen hinein und führt optional den Simulator mit allen oder bestimmten Testfällen aus.
Ist das nicht toll? 😀
Einrichten des VUnit-Skeletts
Um eine VUnit-Testbench zu erstellen, müssen wir unserer Testbench-Datei motor_start_tb
einen bestimmten Code hinzufügen , wie in diesem Unterabschnitt erläutert.
1 – Fügen Sie die Bibliotheken wie folgt hinzu.
Zuerst müssen wir die VUnit-Bibliothek VUNIT_LIB
hinzufügen und sein Kontext:VUNIT_CONTEXT
, sodass wir wie folgt Zugriff auf die VUnit-Funktionalität haben:
LIBRARY VUNIT_LIB; CONTEXT VUNIT_LIB.VUNIT_CONTEXT;
Zweitens müssen wir die Design- und Testbench-Bibliotheken DESIGN_LIB
hinzufügen und TB_LIB
damit wir wie folgt Zugriff auf unser DUT und unsere Pakete haben:
LIBRARY DESIGN_LIB; USE DESIGN_LIB.MOTOR_PKG.ALL; LIBRARY TB_LIB; USE TB_LIB.MOTOR_TB_PKG.ALL;
Das DUT hat zwei Pakete; eine für Design:motor_pkg
und das andere für Testbench-Elemente motor_tb_pkg
. Es sind triviale Pakete, die ich erstellt habe, weil große Projekte normalerweise so strukturiert sind. Ich möchte zeigen, wie VUnit damit umgeht.
motor_start
undmotor_pkg
wird inDESIGN_LIB
kompiliert .motor_start_tb
undmotor_tb_pkg
wird inTB_LIB
kompiliert .
2 – Fügen Sie der Entität wie folgt eine Runner-Konfiguration hinzu:
ENTITY motor_start_tb IS GENERIC(runner_cfg : string := runner_cfg_default); END ENTITY motor_start_tb;
runner_cfg
ist eine generische Konstante, mit der der Python-Testrunner die Testbench steuern kann. Das heißt, wir können Tests aus der Python-Umgebung ausführen. Dieses Generikum ist obligatorisch und ändert sich nicht.
3 – Fügen Sie das VUnit-Testbench-Skelett wie folgt zu unserer Testbench hinzu:
ARCHITECTURE tb OF motor_start_tb IS test_runner : PROCESS BEGIN -- setup VUnit test_runner_setup(runner, runner_cfg); test_cases_loop : WHILE test_suite LOOP -- your testbench test cases here END LOOP test_cases_loop; test_runner_cleanup(runner); -- end of simulation END PROCESS test_runner; END ARCHITECTURE tb;
test_runner
ist der Hauptsteuerungsprozess für die Testbench. Es beginnt immer mit der Prozedur test_runner_setup
und endet mit der Prozedur test_runner_cleanup
. Die Simulation lebt zwischen diesen beiden Verfahren. test_cases_loop
sind unsere Testanzüge, in denen alle unsere Testfälle stattfinden.
Um einen Testfall zu erstellen, verwenden wir den run
von VUnit Funktion innerhalb einer If-Anweisung wie folgt:
IF run("test_case_name") THEN -- test case code here ELSIF run("test_case_name") THEN -- test case code here END IF;
Dann können wir alle oder bestimmte Testfälle aus der Python-Umgebung ausführen, indem wir sie einfach mit dem Namen aufrufen, den wir im Aufruf von run
angegeben haben .
Aufbau der Testbench
Hier beginnen wir mit dem Hinzufügen der erforderlichen Signale zur Kommunikation mit dem DUT, wie unten gezeigt:
-------------------------------------------------------------------------- -- TYPES, RECORDS, INTERNAL SIGNALS, FSM, CONSTANTS DECLARATION. -------------------------------------------------------------------------- -- CONSTANTS DECLARATION -- -- simulation constants CONSTANT C_CLK_PERIOD : time := 10 ns; -- INTERNAL SIGNALS DECLARATION -- -- DUT interface SIGNAL clk : STD_LOGIC := '0'; SIGNAL reset : STD_LOGIC := '1'; SIGNAL motor_start_in : MOTOR_START_IN_RECORD_TYPE := (switch_1 => '0', switch_2 => '0', switch_3 => '0'); SIGNAL motor_start_out : MOTOR_START_OUT_RECORD_TYPE; -- simulation signals SIGNAL led_out : STD_LOGIC_VECTOR(2 DOWNTO 0) := (OTHERS => '0');
Hinweis: Es hat sich bewährt, die Signale mit einem Anfangswert zu initialisieren.
Instanziieren Sie als Nächstes das DUT wie folgt:
-------------------------------------------------------------------------- -- DUT INSTANTIATION. -------------------------------------------------------------------------- motor_start_tb_inst : ENTITY DESIGN_LIB.motor_start(rtl) PORT MAP( clk => clk, reset => reset, motor_start_in => motor_start_in, motor_start_out => motor_start_out );
Hinweis: Ich habe die Eingabeports und Ausgabeports in Datensätzen gruppiert. Ich finde das in großen Projekten von Vorteil, weil es die Entitäten und Instanziierungen weniger überladen macht.
Und schließlich fahren Sie clk
, reset
, und led_out
wie hier gezeigt:
-------------------------------------------------------------------------- -- SIGNAL DEFINITION OF UNUSED OUTPUT PORTS AND FIXED SIGNALS. -------------------------------------------------------------------------- led_out(0) <= motor_start_out.red_led; led_out(1) <= motor_start_out.yellow_led; led_out(2) <= motor_start_out.green_led; -------------------------------------------------------------------------- -- CLOCK AND RESET. -------------------------------------------------------------------------- clk <= NOT clk after C_CLK_PERIOD / 2; reset <= '0' after 5 * (C_CLK_PERIOD / 2);
Entwicklung von Testfällen
Lassen Sie uns nun zu unserem DUT zurückkehren und mit der eigentlichen Arbeit beginnen, indem wir einige Testfälle entwickeln. Ich werde zwei Szenarien vorstellen:
Konstruktionsingenieur-Szenario: aus Sicht des Designers, der Designer selbst führt die Verifizierung durch. In diesem Szenario, das normalerweise während der Entwicklungsphase auftritt, kann der Designer auf den Code zugreifen. Dieses Szenario zeigt, wie VUnit uns hilft, „früh und oft zu testen“.
Verification Engineer-Szenario :Das Design (DUT) wird als Blackbox behandelt. Wir kennen nur die externe Schnittstelle und die Testanforderungen.
Wir werden uns auch diese drei Überprüfungstechniken ansehen:
- Treiber und Checker im Testfall.
- Treiber und kontrollierter Prüfer im Testfall.
- Treiber im Testfall und Selbstprüfer.
Beginnen wir mit der ersten Technik und kommen später in diesem Artikel auf die letzten beiden Methoden zurück.
Treiber und Prüfer im Testfall
Dies ist der einfachste Ansatz. Der Treiber und der Prüfer befinden sich im Testfall selbst, wir implementieren die Fahr- und Prüfvorgänge innerhalb des Testfallcodes.
Nehmen wir an, dass wir die RED_LED-Funktionalität wie folgt entwickelt haben:
WHEN SWITCH_1_ON => IF (motor_start_in.switch_1 = '0' OR motor_start_in.switch_2 = '1' OR motor_start_in.switch_3 = '1') THEN state = WAIT_FOR_SWITCHES_OFF; ELSIF (counter = 0) THEN led_s.red_led <= '1'; state <= WAIT_FOR_SWITCH_2; ELSE led_s.red_led <= NOT led_s.red_led; END IF;
Und jetzt wollen wir unser Design überprüfen, bevor wir mit der Entwicklung der restlichen Funktionalität fortfahren.
Dazu verwenden wir den run
von VUnit Funktion innerhalb des test_suite
um einen Testfall zu erstellen, um die Ausgabe des Einschaltens von switch_1 zu verifizieren, wie unten gezeigt:
IF run("switch_1_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switches_off_output_check"); info("------------------------------------------------------------------"); -- code for your test case here.
Dadurch wird ein Testfall namens „switch_1_on_output_check“
erstellt
Hinweis: info
ist eine VUnit-Prozedur aus der Protokollierungsbibliothek, die auf dem Transcripts-Bildschirm und dem Terminal gedruckt wird. Wir werden es verwenden, um das Testergebnis anzuzeigen.
Jetzt schreiben wir den Code für diesen Testfall. Dazu verwenden wir die Prüfunterprogramme von VUnit.
Wir wissen, dass RED_LED fünfmal blinkt, nachdem switch_1 eingeschaltet wurde, also erstellen wir eine VHDL-For-Schleife und führen die Überprüfung darin durch.
Der check
Das Verfahren führt eine Überprüfung der von uns bereitgestellten spezifischen Parameter durch. Es hat viele Varianten, und hier habe ich einige davon zu Demonstrationszwecken verwendet.
check(expr => motor_start_out.red_led = '1', msg => "Expect red_led to be ON '1'");
Hier wird getestet, ob RED_LED zu diesem Zeitpunkt der Simulationszeit „1“ ist, und eine Nachricht an die Konsole ausgegeben:
# 35001 ps - check - PASS - red_led when switch_1 on (motor_start_tb.vhdl:192)
Hinweis dass es uns mitteilt, ob es ein PASS oder ein ERROR ist, und den Zeitstempel, wann diese Überprüfung stattgefunden hat, zusammen mit dem Dateinamen und der Zeilennummer, wo sich diese Überprüfung befindet.
Eine andere Möglichkeit ist die Verwendung des check_false
Verfahren. Hier verwenden wir es, um zu prüfen, ob YELLOW_LED „0“ ist:
check_false(expr => ??motor_start_out.yellow_led, msg => result("for yellow_led when switch_1 on"));
Hier verwenden wir den result
von VUnit Funktion zur Verbesserung der Nachricht. Der Ausdruck sieht folgendermaßen aus:
# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on # (motor_start_tb.vhdl:193)
Hinweis dass zusätzliche Informationen über den Schecktyp gedruckt werden:„Falscher Scheck bestanden“.
Eine weitere Möglichkeit ist die Verwendung von check_equal
. Hier verwenden wir es zum Testen, dass GREEN_LED „0“ ist:
check_equal(got => motor_start_out.green_led, expected => '0', msg => result("for green_led when switch_1 on"));
Hier haben wir einen zusätzlichen Parameter „0“ für den Vergleich bereitgestellt. Der resultierende Ausdruck ist:
# 35001 ps - check - PASS - Equality check passed for green_led when switch_1 on - # Got 0. (motor_start_tb.vhdl:194)
Jetzt wissen wir, dass RED_LED nach einem Taktzyklus ausgeschaltet wird und die anderen LEDs ebenfalls ausgeschaltet bleiben. Wir können check_equal
verwenden um alle gleichzeitig zu prüfen, wie unten gezeigt:
check_equal(led_out, STD_LOGIC_VECTOR'("000"), result("for led_out when switch_1 on"), warning);
Hinweis die Verwendung des Qualifiers STD_LOGIC_VECTOR'("000")
, sodass die Werte für die Prozedur nicht mehrdeutig sind. Außerdem haben wir die Ebene dieser Prüfung auf WARNING festgelegt, was bedeutet, dass, wenn diese Prüfung fehlschlägt, eine Warnung ausgegeben wird, anstatt einen Fehler zu werfen. Die Ausgabe sieht folgendermaßen aus:
# 45001 ps - check - PASS - Equality check passed for led_out when switch_1 on - # Got 000 (0). (motor_start_tb.vhdl:197)
Dies ist der Code für den vollständigen Testfall:
WAIT UNTIL reset <= '0'; WAIT UNTIL falling_edge(clk); motor_start_in.switch_1 <= '1'; -- turn on switch_1 FOR i IN 0 TO 4 LOOP WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(expr => motor_start_out.red_led = '1', msg => "Expect red_led to be ON '1'"); check_false(expr => ??motor_start_out.yellow_led, msg => result("for yellow_led when switch_1 on")); check_equal(got => motor_start_out.green_led, expected => '0', msg => result("for green_led when switch_1 on")); WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check_equal(led_out, STD_LOGIC_VECTOR'("000"), result("for led_out when switch_1 on"), warning); END LOOP; info("===== TEST CASE FINISHED =====");
Testfall läuft
Jetzt ist es an der Zeit, unseren Testfall auszuführen. Wir können die Tests im Terminal oder in der Simulator-GUI ausführen.
Durchführen eines Tests im Terminal
Öffnen Sie dazu zunächst ein neues Terminal (CMD, PowerShell, Windows Terminal) und navigieren Sie zum vunit_tutorial Verzeichnis, in dem der run.py
Datei befindet.
Um den Testfall auszuführen, geben Sie einfach Folgendes ein:
python .\run.py *switch_1_on_output_check
VUnit kompiliert alle VHDL-Dateien in der richtigen Reihenfolge und analysiert sie auf der Suche nach VUnit-Testbenches. Und dann schaut VUnit in diesen Dateien nach run
Funktion mit dem Testfallnamen „switch_1_on_output_check“, um sie auszuführen.
Hinweis: wir setzen das * Platzhaltersymbol vor den Testfall, um mit seinem vollständigen Namen übereinzustimmen, der lautet:
tb_lib.motor_start_tb.switch_1_on_output_check
Das ist möglich, weil die VUnit-Befehlszeilenschnittstelle Platzhalter akzeptiert.
Der resultierende Ausdruck nach der Simulation ist:
Starting tb_lib.motor_start_tb.switch_1_on_output_check Output file: C:\vunit_tutorial\vunit_out\test_output\tb_lib.motor_start_tb. switch_1_on_output_check_6df3cd7bf77a9a304e02d3e25d028a92fc541cf5\output.txt pass (P=1 S=0 F=0 T=1) tb_lib.motor_start_tb.switch_1_on_output_check (1.1 seconds) ==== Summary ========================================================== pass tb_lib.motor_start_tb.switch_1_on_output_check (1.1 seconds) ======================================================================= pass 1 of 1 ======================================================================= Total time was 1.1 seconds Elapsed time was 1.1 seconds ======================================================================= All passed!
Wir können sehen, dass ein Test gelaufen ist und bestanden wurde.
Hinweis dass VUnit ein vunit_out erstellt hat Ordner im Projektverzeichnis. In diesem Ordner befindet sich ein Ordner namens test_output das hat Berichte über die Tests.
Oben haben wir nur das endgültige Testergebnis ohne Details zu jeder Prüfung erhalten, aber das VUnit-Befehlszeilentool bietet mehrere Schalter zum Ausführen von Tests. Um mehr Informationen darüber zu erhalten, was während der Simulation vor sich geht, können wir den Verbose-Schalter -v
verwenden :
python .\run.py *switch_1_on_output_check -v
Der ausführliche Ausdruck sieht folgendermaßen aus:
Andere nützliche Schalter sind:
-l, --list
:Alle Testfälle auflisten.
--clean
:Lösche zuerst den Ausgabeordner und führe dann den Test aus.
--compile
:Dieser Schalter ist nützlich, wenn Sie kompilieren möchten, ohne Tests durchzuführen, um zum Beispiel nach Fehlern zu suchen.
Durchführen eines Tests in der Simulator-GUI
Oft ist eine Sichtprüfung der Welle erforderlich. VUnit bietet eine automatisierte Möglichkeit, Tests im Simulator auszuführen, indem der GUI-Schalter -g
verwendet wird . VUnit führt die gesamte Kompilierung durch und startet ModelSim (den von uns konfigurierten Simulator) mit dem angeforderten Testfall und fügt die Signale zum Wave-Fenster hinzu, vorausgesetzt, dass ein wave.do
Datei ist verfügbar.
python .\run.py *switch_1_on_output_check -g
Jetzt startet VUnit ModelSim für diesen speziellen Testfall, öffnet das Wellenformfenster und fügt die Signale hinzu, da ich bereits den motor_start_tb_wave.do
habe innerhalb der Wellen Ordner.
Hinweis: Sie können die Wellenform nach Belieben anpassen, aber Sie müssen die Datei im Wellenformformat innerhalb der Wellen speichern Ordner mit dieser Namenskonvention testbench_file_name_wave.do
damit es geladen werden kann, wenn VUnit ModelSim startet.
Angenommen, Sie ändern Ihren Code, nachdem Sie Fehler entdeckt haben, und möchten diesen Testfall erneut ausführen. In diesem Fall können Sie im Transkriptfenster von ModelSim Folgendes eingeben:vunit_restart
, Dadurch wird VUnit neu kompiliert, neu gestartet und die Simulation erneut ausgeführt.
Treiber und kontrollierter Prüfer im Testfall
Bisher haben wir gelernt, wie man eine VUnit-Testbench aufsetzt und den Testfall entwickelt und ausführt. In diesem Abschnitt werden wir weitere Testfälle aus der Sicht des Verifikationsingenieurs entwickeln, wobei wir einen anderen Verifikationsansatz und eine andere VUnit-Checker-Bibliothek verwenden.
Im Gegensatz zum vorherigen Testfall hat dieser Ansatz den Treiber innerhalb des Testfalls und den Prüfer außerhalb, aber der Testfall steuert ihn immer noch.
Nehmen wir an, wir haben diese Verifizierungsanforderung:
- Überprüfen Sie den Ausgang nach dem Einschalten von switch_2, während switch_1 eingeschaltet ist und RED_LED leuchtet.
Aus der DUT-Sektion wissen wir das:
- Wenn Schalter_2 eingeschaltet ist, beginnt YELLOW_LED nach 5 Taktzyklen für 10 Taktzyklen konstant zu leuchten, und danach gehen YELLOW_LED und RED_LED AUS.
Wir verwenden den check_stable
von VUnit Verfahren, um Folgendes zu überprüfen:
- YELLOW_LED ist von Anfang an „0“, bis switch_2 eingeschaltet ist.
- YELLOW_LED ist "1" für 10 Taktzyklen.
Wir verwenden den check_next
von VUnit Verfahren, um Folgendes zu überprüfen:
- YELLOW_LED ist '1' nach 5 Taktzyklen ab dem Einschalten von switch_2.
check_stable :Überprüfen Sie, ob Signale innerhalb eines Fensters stabil sind, das mit einem start_event
beginnt Signalimpuls und endet mit einem end_event
Signalimpuls.
check_next :Überprüfen Sie, ob das Signal nach einer Anzahl von Taktzyklen nach einem start_event
=„1“ ist Signalimpuls.
start_event
und end_event
Signale werden vom Testfall gesteuert.
Wir beginnen mit dem Hinzufügen erforderlicher Signale für check_stable
und check_next
Verfahren wie folgt:
-- VUnit signals SIGNAL enable : STD_LOGIC := '0'; -- for yellow_led SIGNAL yellow_next_start_event : STD_LOGIC := '0'; SIGNAL yellow_low_start_event : STD_LOGIC := '0'; SIGNAL yellow_low_end_event : STD_LOGIC := '0'; SIGNAL yellow_high_start_event : STD_LOGIC := '0'; SIGNAL yellow_high_end_event : STD_LOGIC := '0';
Dann erstellen wir einen neuen Testfall innerhalb des test_suite
mit run
von VUnit funktionieren wie folgt:
ELSIF run("switch_2_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switch_2_on_output_check"); info("------------------------------------------------------------------");
Wir erstellen einen start_event
für den Low-Zustand von YELLOW_LED zur Verwendung mit check_stable
wie folgt vorgehen:
WAIT UNTIL reset <= '0'; -- out of reset enable <= '1'; pulse_high(clk, yellow_low_start_event); WAIT FOR C_CLK_PERIOD * 3;
Die enable
Signal aktiviert den check_stable
und check_next
Verfahren, und wir möchten sie von Anfang an ermöglichen.
pulse_high
ist ein triviales Verfahren von motor_tb_pkg
der auf die nächste steigende Taktflanke wartet und ein Signal für einen Taktzyklus pulsiert. In diesem Fall ist es yellow_ low_start_event
.
Jetzt schalten wir switch_1 ON und warten, bis RED_LED konstant leuchtet, und dann schalten wir switch_2 ON:
-- turn ON switch_1 motor_start_in.switch_1 <= '1'; -- wait until RED_LED finished WAIT FOR C_CLK_PERIOD * 12; -- turn ON switch_2 motor_start_in.switch_2 <= '1';
Jetzt wissen wir, dass YELLOW_LED nach 5 Taktzyklen „1“ sein wird. Daher erstellen wir einen start_event
für YELLOW_LED zur Verwendung mit check_next
Verfahren:
-- after 5 clk cycles YELLOW_LED will light -- next_start_event for YELLOW_LED high pulse_high(clk, yellow_next_start_event); -- 1st clk cycle
Auf ähnliche Weise erstellen wir einen end_event
für den Low-Zustand von YELLOW_LED zur Verwendung mit check_stable
Verfahren:
WAIT FOR C_CLK_PERIOD * 3; -- end event for YELLOW_LED low pulse_high(clk, yellow_low_end_event); -- 5th clk cycle
Jetzt wird YELLOW_LED für 10 Taktzyklen hoch sein. Daher erstellen wir einen start_event
für den hohen Zustand von YELLOW_LED für check_stable
Verfahren:
-- YELLOW_LED is high for 10 clk cycles -- start event for YELLOW_LED high yellow_high_start_event <= '1'; WAIT UNTIL rising_edge(clk); -- 1st clk cycle yellow_high_start_event <= '0';
Hier habe ich den pulse_high
nicht verwendet Verfahren, weil ich möchte, dass der Impuls jetzt auftritt und nicht in der nächsten fallenden Flanke.
Und wir erstellen einen end_event
für den hohen Zustand von YELLOW_LED für check_stable
nach 8 Taktzyklen wie folgt:
WAIT FOR C_CLK_PERIOD * 8; -- end event for YELLOW_LED pulse_high(clk, yellow_high_end_event); -- 10th clk cycle
Damit ist der Testfall abgeschlossen. Wir müssen nur die Aufrufe zu den Checker-Prozeduren nach dem Prozess wie folgt hinzufügen:
---------------------------------------------------------------------- -- Related YELLOW_LED check ---------------------------------------------------------------------- -- check that YELLOW_LED is low from start until switch_2 is ON check_stable(clock => clk, en => enable, start_event => yellow_low_start_event, end_event => yellow_low_end_event, expr => motor_start_out.yellow_led, msg => result("YELLOW_LED Low before switch_2"), active_clock_edge => rising_edge, allow_restart => false); -- check that YELLOW_LED is high after switch_2 is ON check_next(clock => clk, en => enable, start_event => yellow_next_start_event, expr => motor_start_out.yellow_led, msg => result("for yellow_led is high after 5 clk"), num_cks => 5, allow_overlapping => false, allow_missing_start => true); -- check that YELLOW_LED is high after for 10 clk cycle check_stable(clk, enable, yellow_high_start_event, yellow_high_end_event, motor_start_out.yellow_led, result("for YELLOW_LED High after switch_2"));
Hinweis: der allow_restart
Parameter im check_stable
Prozedur ermöglicht den Start eines neuen Fensters, wenn ein neuer start_event
geschieht vor dem end_event
.
Hinweis: wir setzen 5 in check_next
ein Prozedur, weil YELLOW_LED nach 5 Taktzyklen von yellow_next_start_event
hoch sein wird .
Hinweis: die letzten beiden Parameter in check_next
sind:
allow_overlapping
:ein zweitesstart_event
zulassen vor dem expr für den ersten ist ‚1‘.allow_missing_start
:expr darf 1 sein ohnestart_event
.
Jetzt können wir den Testfall wie folgt über die Befehlszeile ausführen:
python .\run.py *switch_2_on_output_check -v
Und das Ergebnis wird wie folgt aussehen:
Wir können den Testfall in der Simulator-GUI wie folgt ausführen:
python .\run.py *switch_2_on_output_check -g
Daraus ergibt sich diese Wellenform:
Hinweis:start_event
und end_event
Signale für check_stable
sind im Fenster enthalten, und die überwachten Signale werden referenziert, wenn start_event='1'
.
In diesem Testfall haben wir die Prüfoperationen aus dem Testfall genommen, sie aber vom Testfall aus gesteuert. Beachten Sie auch, dass wir die RED_LED-Funktionalität nicht überprüft haben.
Treiber im Testfall und Selbstprüfer
Ein Nachteil des vorherigen Ansatzes ist, dass er weniger lesbar ist. Der Testfall enthält Informationen, die nicht interessant sind oder sich nicht auf den Test oder die Funktionalität beziehen, wie z. B. start_event
und end_event
Signale. Wir möchten all diese Details ausblenden, nur den Treiber im Testfall haben und einen Selbstprüfer erstellen.
Beginnen wir mit dem Entwerfen des Treibers.
Die Verfahren von VHDL sind dafür ein guter Kandidat. Wenn Sie nicht wissen, wie man ein VHDL-Verfahren verwendet, sehen Sie sich dieses Tutorial an.
Unten ist das Verfahren switch_driver
ab motor_tb_pkg
.
PROCEDURE switch_driver( SIGNAL switches : OUT MOTOR_START_IN_RECORD_TYPE; CONSTANT clk_period : IN TIME; CONSTANT sw1_delay : IN INTEGER; CONSTANT sw2_delay : IN INTEGER; CONSTANT sw3_delay : IN INTEGER) IS BEGIN IF (sw1_delay = 0) THEN WAIT FOR clk_period * sw1_delay; switches.switch_1 <= '1'; ELSIF (sw1_delay = -1) THEN switches.switch_1 <= '0'; END IF; IF (sw2_delay = 0) THEN WAIT FOR clk_period * sw2_delay; switches.switch_2 <= '1'; ELSIF (sw2_delay = -1) THEN switches.switch_2 <= '0'; END IF; IF (sw3_delay = 0) THEN WAIT FOR clk_period * sw3_delay; switches.switch_3 <= '1'; ELSIF (sw3_delay = -1) THEN switches.switch_3 <= '0'; END IF; END PROCEDURE switch_driver;
Wir liefern die Taktperiode zur Berechnung der Verzögerungen und eine Ganzzahl, die die gewünschte Verzögerung für jeden Schalter in Taktperioden angibt.
- Natürliche Werte (>=0) bedeutet:Schalter einschalten nach (
clk_period
*sw_delay
). - Der Wert -1 bedeutet:Schalter ausschalten.
- Alle anderen negativen Werte bedeuten:nichts tun.
Jetzt können wir diese Prozedur innerhalb des Testfalls wie folgt verwenden:
switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);
Dadurch wird switch_1 nach 3 Taktzyklen eingeschaltet und dann switch_2 nach 12 Taktzyklen und schließlich switch_3 nach 20 Taktzyklen.
Bisher haben wir den Standard-Checker von VUnit verwendet. VUnit bietet die Möglichkeit, einen benutzerdefinierten Checker zu haben. Dies ist nützlich, wenn Sie einen großen Testfall mit vielen Überprüfungen haben und Statistiken über die Überprüfung haben möchten oder die Überprüfung nicht mit dem Rest der Testbench verwechseln möchten.
Wir werden einen benutzerdefinierten Checker für RED_LED erstellen und die Fehlerprotokollebene auf WARNING:
setzenCONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);
Und wir aktivieren die Protokollierung von bestandenen Prüfungen für RED_CHECKER im Abschnitt Setup VUnit:
show(get_logger(RED_CHECKER), display_handler, pass);
Kommen wir nun zum selbstüberprüfenden Checker (oder Monitor). Wir werden zuerst einen selbstüberprüfenden Checker für RED_LED entwerfen und dafür einen Prozess wie folgt verwenden:
- Warten Sie, bis switch_1 eingeschaltet wird, und fügen Sie einen
start_event
hinzu für alle LEDs low:
red_monitor_process : PROCESS BEGIN WAIT UNTIL reset = '0'; pulse_high(clk, led_low_start_event); WAIT UNTIL motor_start_in.switch_1 = '1';
- Wir verwenden dieselbe FOR-SCHLEIFE aus dem ersten Testfall für das Blinken von RED_LED und fügen einen
start_event
hinzu für RED_LED hoch:
-- RED_LED is blinking FOR i IN 0 TO 4 LOOP IF (motor_start_in.switch_1 = '1' AND motor_start_in.switch_2 = '0' AND motor_start_in.switch_3 = '0') THEN WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(RED_CHECKER, motor_start_out.red_led = '1', result("for red_led blink high")); WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(RED_CHECKER, motor_start_out.red_led = '0', result("for red_led blink low")); -- RED_LED is constantly lighting start event IF (i = 4) THEN -- finish blinking WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(RED_CHECKER, motor_start_out.red_led = '1', result("for red_led blink low")); pulse_high(clk, red_high_start_event); END IF; ELSE -- perform check for error (All led high until all switches are off) END IF; END LOOP;
- Jetzt warten wir, bis switch_2 eingeschaltet wird, und fügen dann einen
end_event
hinzu für RED_LED hoch:
IF (motor_start_in.switch_2 /= '1') THEN WAIT UNTIL motor_start_in.switch_2 = '1'; END IF; WAIT UNTIL rising_edge(clk); WAIT FOR C_CLK_PERIOD * 14; -- RED_LED is constantly lighting end event pulse_high(clk, red_high_end_event); END PROCESS red_monitor_process;
- Nun fügen wir den
check_stable
hinzu Verfahren für RED_LED hoch und niedrig:
-- check that RED_LED is low from start until switch_1 is ON -- Note the use of motor_start_in.switch_1 as end_event check_stable(RED_CHECKER, clk, enable, led_low_start_event, motor_start_in.switch_1, motor_start_out.red_led, result("RED_LED low before switch_1")); -- check that RED_LED is low after switch_2 is ON check_stable(RED_CHECKER, clk, enable, red_high_start_event, red_high_end_event, motor_start_out.red_led, result("RED_LED high after switch_1"));
Lassen Sie uns dies mit dem folgenden Testfall testen:
ELSIF run("red_led_output_self-check") THEN info("---------------------------------------------------------------"); info("TEST CASE: red_led_output_self-check"); info("---------------------------------------------------------------"); WAIT UNTIL reset = '0'; -- turn switch_1 on after 3 clk cycles and switch_2 after 13 clk cycles switch_driver(motor_start_in,C_CLK_PERIOD,3,13,-1); WAIT FOR C_CLK_PERIOD * 3; -- dummy wait info("===== TEST CASE FINISHED =====");
Wir führen die Simulation wie folgt aus:
python .\run.py *red_led* -v
Das Ergebnis ist:
Hinweis dass der Checker jetzt red_led_checker
ist .
Wir können den gleichen Ansatz verfolgen, um einen selbstprüfenden Checker für YELLOW_LED zu entwerfen, aber ich überlasse dies als Übung für den Leser. Ich werde jedoch die nächsten verschiedenen Möglichkeiten zeigen, die GREEN_LED-Funktionalität mit check
zu überprüfen , check_stable
, check_next
und check_equal
Verfahren.
Um zu überprüfen, ob GREEN_LED von Anfang an ausgeschaltet ist, bis switch_3 zum ersten Mal eingeschaltet wird, verwenden wir einfach den check_stable
Verfahren:
-- check that GREEN_LED is low from start until switch_3 is ON. check_stable(clk, enable, led_low_start_event, motor_start_in.switch_3, motor_start_out.green_led, result("GREEN_LED low before switch_3"));
Eine Möglichkeit zu überprüfen, ob GREEN_LED eingeschaltet ist, wenn switch_3 eingeschaltet ist, ist die Verwendung von check_next
Verfahren. Wir nennen es mit switch_3 als start_event
, weisen Sie num_cks
1 Taktzyklus zu , und Überlappung zulassen:
-- check that GREEN_LED is high using check_next check_next(clock => clk, en => green_next_en, start_event => motor_start_in.switch_3, expr => motor_start_out.green_led, msg => result("for green_led high using check_next"), num_cks => 1, allow_overlapping => true, allow_missing_start => false);
Da wir Überlappungen zugelassen haben, wird diese Prozedur bei jeder steigenden Taktflanke ausgelöst, wenn switch_3 „1“ ist. t erwartet, dass GREEN_LED nach einem Taktzyklus „1“ ist, und das ist es, was wir wollen.
Eine andere Möglichkeit, die GREEN_LED-Funktionalität zu testen, ist die Verwendung der getakteten Version von check
Prozedur mit einer verzögerten Version von switch_3 als Enable für diese Prozedur:
switch_3_delayed <= motor_start_in.switch_3'delayed(C_CLK_PERIOD + 1 ps) AND NOT enable; check(clock => clk, en => switch_3_delayed, expr => motor_start_out.green_led, msg => result("for green_led high using delayed"));
switch_3_delayed
ist ein um 1 Taktzyklus verzögertes Signal von switch_3. Daher wird diese Prozedur immer 1 Taktzyklus aktiviert, nachdem switch_3 „1“ ist, und die Prozedur prüft, ob GREEN_LED „1“ ist, wenn sie aktiviert ist.
Die switch_3_delayed
signal ist eine um einen Taktzyklus verzögerte Version switch_3. Somit aktiviert er diese Prozedur immer einen Taktzyklus, nachdem switch_3 „1“ ist. Wenn aktiviert, prüft die Prozedur, ob GREEN_LED „1“ ist.
Hinweis: das UND NICHT aktivieren in switch_3_delayed
dient nur dazu, dieses Signal zu maskieren, wenn wir nicht den Prozessansatz verwenden.
Schließlich können wir einen dedizierten Prozess mit einer VHDL-While-Schleife verwenden, um die Prüfung auf GREEN_LED:
durchzuführengreen_monitor_process : PROCESS BEGIN WAIT UNTIL enable = '1' AND motor_start_in.switch_1 = '1' AND motor_start_in.switch_2 = '1' AND motor_start_in.switch_3 = '1'; WHILE motor_start_in.switch_3 = '1' LOOP WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check_equal(led_out, STD_LOGIC_VECTOR'("100"), result("for led_out when switch_3 on")); END LOOP; END PROCESS green_monitor_process;
Nun entwickeln wir einen Testfall für GREEN_LED wie folgt:
ELSIF run("switch_3_on_output_check") THEN info("-------------------------------------------------------------"); info("TEST CASE: switch_3_on_output_check"); info("-------------------------------------------------------------"); info("check using a clocked check PROCEDURES"); WAIT UNTIL reset = '0'; switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20); WAIT FOR C_CLK_PERIOD * 5; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1); WAIT FOR C_CLK_PERIOD * 3; -- dummy wait info("-------------------------------------------------------------"); info("check using check_next PROCEDURES"); green_next_en <= '1'; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,10); WAIT FOR C_CLK_PERIOD * 5; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1); WAIT FOR C_CLK_PERIOD * 3; -- dummy wait green_next_en <= '0'; info("-------------------------------------------------------------"); info("check using check_equal process"); enable <= '1'; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,10); WAIT FOR C_CLK_PERIOD * 5; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1); WAIT FOR C_CLK_PERIOD * 10; -- dummy wait info("===== TEST CASE FINISHED =====");
Nachdem Sie den Testfall geschrieben haben, können Sie ihn ausführen und die verschiedenen Ergebnisse überprüfen.
Wir können sehen, dass die Testfälle im selbstüberprüfenden Ansatz viel besser lesbar sind, selbst für Ingenieure ohne VHDL-Kenntnisse.
Es gibt weitere Testfälle in der Testbench-Datei. Wir können sie mit diesem Befehl starten:
python .\run.py *motor_start_tb* --list
Und dies ist die Ausgabe, die auf der Konsole ausgegeben wird:
tb_lib.motor_start_tb.switches_off_output_check tb_lib.motor_start_tb.switch_1_on_output_check tb_lib.motor_start_tb.switch_2_on_output_check tb_lib.motor_start_tb.red_led_output_self-check tb_lib.motor_start_tb.switch_3_on_output_check tb_lib.motor_start_tb.switch_2_error_output_check Listed 6 tests
Wir können alle Testfälle ausführen, indem wir Folgendes eingeben:
python .\run.py
Und das Ergebnis ist wie folgt:
==== Summary ============================================================= pass tb_lib.motor_start_tb.switch_1_on_output_check (0.4 seconds) pass tb_lib.motor_start_tb.switch_2_on_output_check (0.4 seconds) pass tb_lib.motor_start_tb.red_led_output_self-check (0.4 seconds) pass tb_lib.motor_start_tb.switch_3_on_output_check (0.4 seconds) fail tb_lib.motor_start_tb.switches_off_output_check (0.9 seconds) fail tb_lib.motor_start_tb.switch_2_error_output_check (0.4 seconds) ========================================================================== pass 4 of 6 fail 2 of 6 ========================================================================== Total time was 2.9 seconds Elapsed time was 2.9 seconds ========================================================================== Some failed!
Zusammenfassung
Das VUnit-Framework bietet erweiterte Funktionen für die Testlaufautomatisierung und umfangreiche Bibliotheken für die Entwicklung von Testbenches. Darüber hinaus ermöglicht es Konstrukteuren, ihre Konstruktion frühzeitig und häufig zu testen.
VUnit hilft auch Verifikationsingenieuren, Testbenches schneller und einfacher zu entwickeln und auszuführen. Am wichtigsten ist, dass es eine schnelle und leichte Lernkurve hat.
Wohin es von hier aus geht
- VUnit-Dokumentation
- VUnit-Blog
- VUnit-Gitter
Laden Sie das Beispielprojekt mit dem unten stehenden Formular herunter!
VHDL
- Code Ready Container:Erste Schritte mit Prozessautomatisierungstools in der Cloud
- Erste Schritte mit dem Keramik-3D-Druck
- Kennenlernen grundlegender Farbstoffe!
- Erste Schritte mit TJBot
- Erste Schritte mit dem RAK 831 Lora Gateway und RPi3
- Erste Schritte mit dem RAK831 LoRa Gateway und RPi3
- Einstieg ins Geschäft mit IoT
- Erste Schritte mit KI im Versicherungswesen:Ein Einführungsleitfaden
- Arduino-Tutorial 01:Erste Schritte
- Erste Schritte mit My.Cat.com