Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Industrial programming >> VHDL

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:

Außerdem werden grundlegende VHDL-Kenntnisse und ModelSim-Kenntnisse vorausgesetzt.

Installieren

git clone --recurse-submodules https://github.com/VUnit/vunit.git
 
python setup.py install

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:

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:

  1. Lade Konfiguration
  2. Kalibrierung
  3. Rotation

Startsequenz

Hier folgen die Startsequenzen des Motors und die Bedeutung der LED-Anzeigen.

  1. Schalter_1 einschalten.
  2. Die RED_LED beginnt 5 Mal zu blinken.
  3. Warten Sie, bis die RED_LED aufhört zu blinken und konstant leuchtet.
  1. Jetzt können Sie Schalter_2 einschalten.
  2. 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.
  1. 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.
  2. 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:

  1. Einrichten des Python-Laufskripts
  2. Einrichten des VUnit-Skeletts
  3. 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.

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:

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:

Aus der DUT-Sektion wissen wir das:

Wir verwenden den check_stable von VUnit Verfahren, um Folgendes zu überprüfen:

Wir verwenden den check_next von VUnit Verfahren, um Folgendes zu überprüfen:

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:

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.

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:

setzen
CONSTANT 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:

red_monitor_process : PROCESS
BEGIN
  WAIT UNTIL reset = '0';
  pulse_high(clk, led_low_start_event);
  WAIT UNTIL motor_start_in.switch_1 = '1';
-- 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;
  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;
-- 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ühren
green_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

Laden Sie das Beispielprojekt mit dem unten stehenden Formular herunter!


VHDL

  1. Code Ready Container:Erste Schritte mit Prozessautomatisierungstools in der Cloud
  2. Erste Schritte mit dem Keramik-3D-Druck
  3. Kennenlernen grundlegender Farbstoffe!
  4. Erste Schritte mit TJBot
  5. Erste Schritte mit dem RAK 831 Lora Gateway und RPi3
  6. Erste Schritte mit dem RAK831 LoRa Gateway und RPi3
  7. Einstieg ins Geschäft mit IoT
  8. Erste Schritte mit KI im Versicherungswesen:Ein Einführungsleitfaden
  9. Arduino-Tutorial 01:Erste Schritte
  10. Erste Schritte mit My.Cat.com