Aan de slag met VUnit
VUnit is een van de meest populaire open-source VHDL-verificatieframeworks die vandaag beschikbaar zijn. Het combineert een Python-testsuite-runner met een speciale VHDL-bibliotheek om uw testbanken te automatiseren.
Om u deze gratis VUnit-zelfstudie te geven, schakelt VHDLwhiz Ahmadmunthar Zaklouta in, die achter de rest van dit artikel staat, inclusief het eenvoudige VUnit-voorbeeldproject dat u kunt downloaden en uitvoeren op uw computer.
Laten we Ahmad het woord geven!
Deze tutorial is bedoeld om het gebruik van het VUnit-framework in het verificatieproces van uw ontwerp te demonstreren. Het begeleidt u bij het opzetten van VUnit, het maken van de VUnit-testbench, het gebruik van de VUnit-controlebibliotheek en het uitvoeren van VUnit-tests in ModelSim. Het demonstreert ook enkele verificatietechnieken.
Overzicht
Dit artikel bevat meerdere screenshots. Klik op de afbeeldingen om ze groter te maken!
Gebruik de zijbalk om door de omtrek te navigeren voor deze zelfstudie, of scrol omlaag en klik op de pop-upnavigatieknop in de rechterbovenhoek als u een mobiel apparaat gebruikt.
Vereisten
Deze tutorial gaat ervan uit dat deze software op een Windows-computer is geïnstalleerd:
- Intel ModelSim
- Raadpleeg dit artikel voor informatie over het gratis installeren van ModelSim.
- ModelSim zou in je PATH moeten staan.
- Python 3.6 of hoger.
- Python downloaden
- Python zou in je PATH moeten staan.
- GIT (optioneel).
- GIT downloaden
- Windows-terminal (optioneel)
- Windows-terminal downloaden
Het veronderstelt ook dat je basis VHDL-kennis en ModelSim-vaardigheden hebt.
Installeren
- VUnit ophalen:
- Als je GIT hebt, kun je het van GitHub naar je C-schijf klonen:
git clone --recurse-submodules https://github.com/VUnit/vunit.git
- Anders kun je het downloaden als een zip van GitHub en het uitpakken naar je C-schijf:
- VUnit downloaden.
- Anders kun je het downloaden als een zip van GitHub en het uitpakken naar je C-schijf:
- VUnit installeren:
- Open een terminal en ga naar
C:\vunit
en typ het volgende commando:
- Open een terminal en ga naar
python setup.py install
- VUnit configureren:
- Voeg omgevingsvariabelen toe aan uw systeem zoals hieronder.
VUNIT_MODELSIM_PATH
:pad naar ModelSim in uw machine.VUNIT_SIMULATOR
:ModelSim
Download het voorbeeldproject
U kunt het voorbeeldproject en de VHDL-code downloaden via het onderstaande formulier.
Pak de zip uit naar C:\vunit_tutorial .
Inleiding
VUnit is een testraamwerk voor HDL dat het verificatieproces vereenvoudigt door een testgestuurde workflow te bieden "vroeg en vaak testen" en een toolbox voor automatisering en beheer van testuitvoeringen. Het is een geavanceerd framework met uitgebreide uitgebreide functies, maar toch gemakkelijk te gebruiken en aan te passen. Het is volledig open-source en kan gemakkelijk worden opgenomen in traditionele testmethoden.
VUnit bestaat uit twee hoofdcomponenten:
- Python-bibliotheek: biedt tools die helpen bij het automatiseren, beheren en configureren van tests.
- VHDL-bibliotheek: een set bibliotheken die helpt bij veelvoorkomende verificatietaken.
Het VHDL-gedeelte bestaat uit zes bibliotheken, zoals weergegeven in het onderstaande diagram. Deze tutorial gebruikt de log- en controlebibliotheken.
Ontwerp wordt getest
Het ontwerp dat in deze zelfstudie wordt gebruikt, genaamd motor_start
, implementeert een startprocedure voor een bepaalde motor en stuurt 3 LED's aan die de status van de motor weergeven.
De interface bestaat uit een invoerrecord motor_start_in
met 3 elementen (3 schakelaars als input) en een output record motor_start_out
met 3 elementen (3 LED's ROOD, GEEL, GROEN als uitgangen).
Sommige motoren moeten in het begin worden geïnitialiseerd voordat u ze kunt gaan gebruiken. Onze motor opstartprocedure bestaat uit drie stappen:
- Laadconfiguratie
- Kalibratie
- Rotatie
Opstartvolgorde
Hier volgt de opstartvolgorde van de motor en de betekenis van de LED-indicatoren.
- RED_LED staat voor laadconfiguratie .
- Schakel schakelaar_1 in.
- De RODE_LED begint 5 keer te knipperen.
- Wacht tot de RED_LED stopt met knipperen en constant brandt.
- GELE_LED staat voor laadkalibratie .
- Je kunt nu switch_2 inschakelen.
- Als switch_2 is ingeschakeld, gaat de GELE_LED na 5 klokcycli constant branden, wat 10 klokcycli aanhoudt. En daarna gaan GELE_LED en RED_LED UIT.
- GREEN_LED geeft aan dat de motor draait.
- Nu is de motor klaar voor gebruik. Elke keer dat switch_3 AAN wordt gezet, zal GREEN_LED constant gaan branden totdat switch_3 UIT wordt gezet.
- Elke overtreding van deze reeks zal ertoe leiden dat alle 3 de LED's constant branden totdat alle schakelaars UIT worden gezet en de reeks opnieuw kan worden gestart.>
Testbench-ontwikkeling
Dit deel van de tutorial bestaat uit de volgende subsecties:
- Het lopende script voor Python instellen
- Het VUnit-skelet opzetten
- De testbank opzetten
Het Python-script run.py instellen
Elk VUnit-project heeft een pythonscript op het hoogste niveau nodig run.py
dat fungeert als een toegangspunt tot het project en het specificeert alle VHDL-ontwerp-, testbench- en bibliotheekbronnen.
Dit bestand bevindt zich meestal in de projectdirectory. De directorystructuur die in deze tutorial wordt gebruikt, ziet er als volgt uit:
In de run.py
bestand, moeten we drie dingen doen:
1 – Haal het pad op waar dit bestand bestaat en specificeer het pad voor ontwerp- en testbench-bestanden.
# ROOT ROOT = Path(__file__).resolve().parent # Sources path for DUT DUT_PATH = ROOT / "design" # Sources path for TB TEST_PATH = ROOT / "testbench"
2 – Maak de VUnit-instantie.
Hiermee wordt een VUnit-instantie gemaakt en toegewezen aan een variabele met de naam VU
. Dan kunnen we VU
. gebruiken om bibliotheken en verschillende taken te maken.
# create VUnit instance VU = VUnit.from_argv() VU.enable_location_preprocessing()
3 – Maak bibliotheken en voeg er VHDL-bronnen aan toe.
Ik vind het leuk om het ontwerpgedeelte van het testbankgedeelte te scheiden. Daarom zullen we twee bibliotheken maken:design_lib
en 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"])
De rest van het bestand is een configuratie voor ModelSim om de wave.do
. te gebruiken bestand als het bestaat.
Opmerking: hier gebruik ik de *.vhdl
verlenging. Mogelijk moet u deze wijzigen als u *.vhd
. gebruikt .
Als u van deze werkstructuur houdt, hoeft u dit bestand helemaal niet te wijzigen. Telkens wanneer u een nieuw project start, kopieert u het gewoon naar uw projectdirectory. Als u echter de voorkeur geeft aan een andere directorystructuur, moet u de paden aanpassen om te voldoen aan uw werkstructuur.
Wanneer we dit bestand nu gebruiken, scant VUnit automatisch naar VUnit-testbanken in uw project, bepaalt de compileervolgorde, maakt de bibliotheken en compileert er bronnen in, en voert optioneel de simulator uit met alle of specifieke testgevallen.
Is dat niet geweldig?
Het VUnit-skelet opzetten
Om een VUnit-testbench te maken, moeten we een specifieke code toevoegen aan ons testbench-bestand motor_start_tb
, zoals uitgelegd in deze paragraaf.
1 – Voeg de bibliotheken als volgt toe.
Eerst moeten we de VUnit-bibliotheek VUNIT_LIB
. toevoegen en de context:VUNIT_CONTEXT
, zodat we als volgt toegang hebben tot de VUnit-functionaliteit:
LIBRARY VUNIT_LIB; CONTEXT VUNIT_LIB.VUNIT_CONTEXT;
Ten tweede moeten we de ontwerp- en testbench-bibliotheken DESIGN_LIB
. toevoegen en TB_LIB
zodat we als volgt toegang hebben tot onze TU Delft en pakketten:
LIBRARY DESIGN_LIB; USE DESIGN_LIB.MOTOR_PKG.ALL; LIBRARY TB_LIB; USE TB_LIB.MOTOR_TB_PKG.ALL;
De TU Delft heeft twee pakketten; één voor ontwerp:motor_pkg
en de andere voor testbench-elementen motor_tb_pkg
. Het zijn triviale pakketten die ik heb gemaakt, omdat dit meestal de manier is waarop grote projecten zijn gestructureerd. Ik wil laten zien hoe VUnit daarmee omgaat.
motor_start
enmotor_pkg
wordt gecompileerd totDESIGN_LIB
.motor_start_tb
enmotor_tb_pkg
wordt gecompileerd totTB_LIB
.
2 – Voeg runner-configuratie als volgt toe aan de entiteit:
ENTITY motor_start_tb IS GENERIC(runner_cfg : string := runner_cfg_default); END ENTITY motor_start_tb;
runner_cfg
is een generieke constante waarmee de python-testrunner de testbank kan besturen. Dat wil zeggen, we kunnen tests uitvoeren vanuit de python-omgeving. Deze generiek is verplicht en verandert niet.
3 – Voeg het VUnit testbench skelet als volgt toe aan onze testbench:
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
is het belangrijkste controleproces voor de testbank. Het begint altijd met de procedure test_runner_setup
en eindigt met de procedure test_runner_cleanup
. De simulatie leeft tussen deze twee procedures. test_cases_loop
is onze testkostuum waar al onze testgevallen plaatsvinden.
Om een testcase te maken, gebruiken we VUnit's run
functie binnen een If-statement als volgt:
IF run("test_case_name") THEN -- test case code here ELSIF run("test_case_name") THEN -- test case code here END IF;
Vervolgens kunnen we alle of specifieke testgevallen uitvoeren vanuit de Python-omgeving door ze eenvoudigweg aan te roepen met de naam die we hebben opgegeven in de aanroep van run
.
De testbank opzetten
Hier beginnen we met het toevoegen van de benodigde signalen om met de TU Delft te communiceren, zoals hieronder weergegeven:
-------------------------------------------------------------------------- -- 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');
Opmerking: Het is een goede gewoonte om de signalen te initialiseren met een beginwaarde.
Instantieer vervolgens de DUT als volgt:
-------------------------------------------------------------------------- -- 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 );
Opmerking: Ik heb de invoerpoorten en uitvoerpoorten gegroepeerd in records. Ik vind dit nuttig bij grote projecten omdat het de entiteiten en instanties minder rommelig maakt.
En tot slot, rijd clk
, reset
, en led_out
zoals hier getoond:
-------------------------------------------------------------------------- -- 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);
Ontwikkeling van testcases
Laten we nu teruggaan naar onze TU Delft en het eigenlijke werk beginnen door enkele testgevallen te ontwikkelen. Ik zal twee scenario's presenteren:
Scenario voor ontwerpingenieur: vanuit het oogpunt van de ontwerper voert de ontwerper zelf de verificatie uit. In dit scenario, dat meestal tijdens de ontwikkelingsfase gebeurt, heeft de ontwerper toegang tot de code. Dit scenario laat zien hoe VUnit ons helpt om 'vroeg en vaak te testen'.
Scenario voor verificatie-engineer :het ontwerp (DUT) wordt behandeld als een zwarte doos. We kennen alleen de externe interface en testvereisten.
We zullen ook kijken naar deze drie verificatietechnieken:
- Driver en checker binnen de testcase.
- Driver en gecontroleerde checker binnen de testcase.
- Driver in de testcase en autocontrole-checker.
Laten we beginnen met de eerste techniek en later in dit artikel terugkomen op de laatste twee methoden.
Driver en checker in de testcase
Dit is de meest directe benadering. De bestuurder en de controleur bevinden zich in de testcase zelf, we implementeren de rij- en controlehandelingen binnen de testcasecode.
Laten we aannemen dat we de RED_LED-functionaliteit als volgt hebben ontwikkeld:
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;
En nu willen we ons ontwerp verifiëren voordat we de rest van de functionaliteit gaan ontwikkelen.
Om dat te doen, gebruiken we VUnit's run
functie binnen de test_suite
om een testcase te maken voor het verifiëren van de uitvoer van het inschakelen van schakelaar_1, zoals hieronder weergegeven:
IF run("switch_1_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switches_off_output_check"); info("------------------------------------------------------------------"); -- code for your test case here.
Hiermee wordt een testcase gemaakt met de naam "switch_1_on_output_check"
Opmerking: info
is een VUnit-procedure van de logging-bibliotheek die wordt afgedrukt naar het transcriptenscherm en de terminal. We zullen het gebruiken om het testresultaat weer te geven.
Nu gaan we de code voor deze testcase schrijven. We zullen daarvoor de controle-subprogramma's van VUnit gebruiken.
We weten dat RED_LED 5 keer knippert nadat switch_1 is ingeschakeld, dus we maken een VHDL For-lus en voeren de controle erin uit.
De check
procedure voert een controle uit op de specifieke parameters die we verstrekken. Het heeft veel varianten, en hier heb ik er een aantal gebruikt voor demonstratiedoeleinden.
check(expr => motor_start_out.red_led = '1', msg => "Expect red_led to be ON '1'");
Hier zal het testen of RED_LED op dit punt van simulatietijd '1' is en een bericht naar de console afdrukken:
# 35001 ps - check - PASS - red_led when switch_1 on (motor_start_tb.vhdl:192)
Opmerking dat het ons vertelt of het een PASS of een FOUT is, en het tijdstempel waarop deze controle plaatsvond, samen met de bestandsnaam en het regelnummer waar deze controle zich bevindt.
Een andere manier is om de check_false
. te gebruiken procedure. Hier gebruiken we het om te controleren of GEEL_LED '0' is:
check_false(expr => ??motor_start_out.yellow_led, msg => result("for yellow_led when switch_1 on"));
Hier gebruiken we VUnit's result
functie om de boodschap te verbeteren. De afdruk ziet er als volgt uit:
# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on # (motor_start_tb.vhdl:193)
Opmerking dat het extra informatie afdrukt over het type cheque:"Valse controle geslaagd".
Nog een andere manier is om check_equal
. te gebruiken . Hier gebruiken we het om te testen of GREEN_LED '0' is:
check_equal(got => motor_start_out.green_led, expected => '0', msg => result("for green_led when switch_1 on"));
Hier hebben we voor de vergelijking een extra parameter '0' gegeven. De resulterende afdruk is:
# 35001 ps - check - PASS - Equality check passed for green_led when switch_1 on - # Got 0. (motor_start_tb.vhdl:194)
Nu weten we dat na één klokcyclus RED_LED UIT gaat en de andere LED's ook uit blijven. We kunnen check_equal
. gebruiken om ze allemaal tegelijk te controleren, zoals hieronder weergegeven:
check_equal(led_out, STD_LOGIC_VECTOR'("000"), result("for led_out when switch_1 on"), warning);
Opmerking het gebruik van de kwalificatie STD_LOGIC_VECTOR'("000")
, dus de waarden zijn niet dubbelzinnig voor de procedure. We hebben ook het niveau van deze controle gespecificeerd als WAARSCHUWING, wat betekent dat als deze controle mislukt, er een waarschuwing wordt gegeven in plaats van een fout te genereren. De uitvoer ziet er als volgt uit:
# 45001 ps - check - PASS - Equality check passed for led_out when switch_1 on - # Got 000 (0). (motor_start_tb.vhdl:197)
Dit is de code voor de volledige testcase:
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 =====");
Testcase wordt uitgevoerd
Nu is het tijd om onze testcase uit te voeren. We kunnen de tests uitvoeren in de terminal of in de GUI van de simulator.
Een test uitvoeren in de terminal
Om dat te doen, begint u met het openen van een nieuwe terminal (CMD, PowerShell, Windows Terminal) en navigeert u naar de vunit_tutorial directory waar de run.py
bestand is gelokaliseerd.
Om de testcase uit te voeren, typt u gewoon:
python .\run.py *switch_1_on_output_check
VUnit compileert alle VHDL-bestanden in de juiste volgorde en parseert ze, op zoek naar VUnit-testbanken. En dan zal VUnit in die bestanden kijken, op zoek naar een run
functie met de testcasenaam "switch_1_on_output_check" om deze uit te voeren.
Opmerking: we plaatsen het jokerteken * vóór de testcase om overeen te komen met de volledige naam, namelijk:
tb_lib.motor_start_tb.switch_1_on_output_check
We kunnen dat doen omdat de VUnit-opdrachtregelinterface jokertekens accepteert.
De resulterende afdruk na simulatie is:
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!
We kunnen zien dat één test is uitgevoerd en dat het een PASS was.
Opmerking dat VUnit een vunit_out heeft gemaakt map in de projectmap. In deze map bevindt zich een map met de naam test_output die rapporten heeft over de tests.
Hierboven kregen we alleen het uiteindelijke testresultaat zonder details over elke controle, maar de VUnit-opdrachtregeltool biedt verschillende schakelaars voor het uitvoeren van tests. Voor meer informatie over wat er tijdens de simulatie gebeurt, kunnen we de uitgebreide schakelaar -v
gebruiken :
python .\run.py *switch_1_on_output_check -v
De uitgebreide afdruk ziet er als volgt uit:
Andere handige schakelaars zijn:
-l, --list
:lijst van alle testgevallen.
--clean
:verwijder eerst de uitvoermap en voer dan de test uit.
--compile
:Deze schakelaar is handig als u wilt compileren zonder tests uit te voeren om bijvoorbeeld op fouten te controleren.
Een test uitvoeren in de simulator-GUI
Vaak is een visuele inspectie van de golf nodig. VUnit biedt een geautomatiseerde manier om tests in de simulator uit te voeren met behulp van de GUI-schakelaar -g
. VUnit doet alle compilatie en start ModelSim (de simulator die we hebben geconfigureerd) met de gevraagde testcase en voegt de signalen toe aan het golfvenster, aangezien een wave.do
bestand is beschikbaar.
python .\run.py *switch_1_on_output_check -g
Nu zal VUnit ModelSim starten voor deze specifieke testcase, het golfvormvenster openen en de signalen toevoegen omdat ik de motor_start_tb_wave.do
al heb binnen de golven map.
Opmerking: U kunt de golfvorm naar wens aanpassen, maar u moet het bestand met de golfvormindeling opslaan in de golven map met deze naamconventie testbench_file_name_wave.do
zodat het kan worden geladen wanneer VUnit ModelSim start.
Stel dat u uw code wijzigt nadat u fouten heeft ontdekt en deze testcase opnieuw wilt uitvoeren. In dat geval kunt u in het transcriptvenster van ModelSim typen:vunit_restart
, Dat zorgt ervoor dat VUnit de simulatie opnieuw compileert, herstart en opnieuw uitvoert.
Driver en gecontroleerde checker in de testcase
Tot nu toe hebben we geleerd hoe we een VUnit-testbench moeten opzetten en de testcase kunnen ontwikkelen en uitvoeren. In deze sectie zullen we meer testcases ontwikkelen vanuit het oogpunt van de verificatie-engineer, met behulp van een andere verificatiebenadering en VUnit-controlebibliotheek.
In tegenstelling tot de vorige testcase, heeft deze aanpak de bestuurder in de testcase en de checker buiten, maar de testcase bestuurt hem nog steeds.
Laten we aannemen dat we deze verificatievereiste hebben:
- Controleer de uitvoer na het inschakelen van switch_2 terwijl switch_1 AAN is en RED_LED brandt.
Van de TU Delft-sectie weten we dat:
- Als switch_2 is ingeschakeld, gaat de GELE_LED constant branden na 5 klokcycli gedurende 10 klokcycli, en daarna gaan de GELE_LED en RED_LED UIT.
We zullen VUnit's check_stable
. gebruiken procedure om te verifiëren dat:
- GEEL_LED is '0' vanaf het begin totdat switch_2 AAN is.
- GEEL_LED is '1' voor 10 klokcycli.
We gebruiken VUnit's check_next
procedure om te verifiëren dat:
- GEEL_LED is '1' na 5 klokcycli vanaf het inschakelen van switch_2.
check_stable :controleer of signaal[en] stabiel zijn in een venster dat begint met een start_event
signaalpuls en eindigt met een end_event
signaalpuls.
check_next :controleer dat signaal =‘1’ na een aantal klokcycli na een start_event
signaalpuls.
start_event
en end_event
signalen worden aangestuurd vanuit de testcase.
We beginnen met het toevoegen van vereiste signalen voor check_stable
en check_next
procedures als volgt:
-- 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';
Vervolgens maken we een nieuwe testcase in de test_suite
met behulp van VUnit's run
functioneren als volgt:
ELSIF run("switch_2_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switch_2_on_output_check"); info("------------------------------------------------------------------");
We maken een start_event
voor de lage status van YELLOW_LED voor gebruik met de check_stable
procedure als volgt:
WAIT UNTIL reset <= '0'; -- out of reset enable <= '1'; pulse_high(clk, yellow_low_start_event); WAIT FOR C_CLK_PERIOD * 3;
De enable
signaal activeert de check_stable
en check_next
procedures, en we willen ze vanaf het begin inschakelen.
pulse_high
is een triviale procedure van motor_tb_pkg
die wacht op de volgende stijgende klokflank en een signaal pulseert gedurende één klokcyclus. In dit geval is het yellow_ low_start_event
.
Nu zetten we switch_1 AAN en wachten tot de RED_LED constant brandt, en dan zetten we switch_2 AAN:
-- 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';
Nu weten we dat GELE_LED na 5 klokcycli '1' zal zijn. Daarom maken we een start_event
voor YELLOW_LED om te gebruiken met de check_next
procedure:
-- 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
Op dezelfde manier maken we een end_event
voor de lage status van YELLOW_LED om te gebruiken met de check_stable
procedure:
WAIT FOR C_CLK_PERIOD * 3; -- end event for YELLOW_LED low pulse_high(clk, yellow_low_end_event); -- 5th clk cycle
Nu zal GEEL_LED gedurende 10 klokcycli hoog zijn. Daarom maken we een start_event
voor de hoge status van YELLOW_LED voor de check_stable
procedure:
-- 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 heb ik de pulse_high
. niet gebruikt procedure omdat ik wil dat de puls nu plaatsvindt, en niet in de volgende dalende flank.
En we maken een end_event
voor de hoge status van YELLOW_LED voor check_stable
na 8 klokcycli als volgt:
WAIT FOR C_CLK_PERIOD * 8; -- end event for YELLOW_LED pulse_high(clk, yellow_high_end_event); -- 10th clk cycle
Daarmee is de testcase klaar. We hoeven alleen de aanroepen toe te voegen aan de checker-procedures na het proces als volgt:
---------------------------------------------------------------------- -- 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"));
Opmerking: de allow_restart
parameter in de check_stable
procedure staat een nieuw venster toe om te starten als een nieuwe start_event
gebeurt vóór de end_event
.
Opmerking: we zetten 5 in check_next
procedure omdat YELLOW_LED hoog zal zijn na 5 klokcycli van yellow_next_start_event
.
Opmerking: de laatste twee parameters in check_next
zijn:
allow_overlapping
:sta een tweedestart_event
toe voor de expr voor de eerste is '1'.allow_missing_start
:laat expr '1' zijn zonder eenstart_event
.
Nu kunnen we de testcase als volgt vanaf de opdrachtregel uitvoeren:
python .\run.py *switch_2_on_output_check -v
En het resultaat is als volgt:
We kunnen de testcase als volgt in de simulator-GUI draaien:
python .\run.py *switch_2_on_output_check -g
Resulterend in deze golfvorm:
Opmerking:start_event
en end_event
signalen voor check_stable
zijn inbegrepen in het venster, en er wordt verwezen naar de bewaakte signaal[en] wanneer start_event='1'
.
In deze testcase hebben we de controlebewerkingen uit de testcase gehaald, maar gecontroleerd vanuit de testcase. Merk ook op dat we niet hebben gecontroleerd op RED_LED-functionaliteit.
Driver in de testcase en autocontrole-checker
Een nadeel van de vorige aanpak is dat het minder leesbaar is. De testcase bevat informatie die niet interessant is of gerelateerd is aan de test of de functionaliteit, zoals de start_event
en end_event
signalen. We willen al deze details verbergen, alleen de bestuurder in de testcase hebben en een autocontrolecontrole maken.
Laten we beginnen met het ontwerpen van de driver.
De procedures van VHDL zijn daarvoor een goede kandidaat. Als je niet weet hoe je een VHDL-procedure moet gebruiken, bekijk dan deze tutorial.
Hieronder vindt u de procedure switch_driver
van 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;
We bieden de klokperiode voor het berekenen van de vertragingen en een geheel getal dat de gewenste vertraging voor elke omschakeling in klokperioden aangeeft.
- Natuurlijke waarden (>=0) betekent:zet de schakelaar aan na (
clk_period
*sw_delay
). - De waarde -1 betekent:zet de schakelaar uit.
- Alle andere negatieve waarden betekent:niets doen.
Nu kunnen we deze procedure als volgt in de testcase gebruiken:
switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);
Hiermee schakelt u switch_1 in na 3 klokcycli en vervolgens schakelt u switch_2 in na 12 klokcycli en tenslotte schakelt u switch_3 in na 20 klokcycli.
Tot nu toe hebben we de standaardcontrole van VUnit gebruikt. VUnit biedt de mogelijkheid om een checker op maat te hebben. Dit is handig als je een grote testcase hebt met veel controle, en je wilt statistieken over de controle hebben, of je wilt de controle niet verwisselen met de rest van de testbench.
We zullen een aangepaste checker voor RED_LED maken en het niveau van het falende log instellen op WAARSCHUWING:
CONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);
En we schakelen het loggen van passerende controles in voor RED_CHECKER in de sectie VUnit instellen:
show(get_logger(RED_CHECKER), display_handler, pass);
Laten we nu overgaan naar de autocontrole-checker (of monitor). We zullen eerst een zelfcontrolerende checker voor RED_LED ontwerpen, en we zullen hiervoor een proces als volgt gebruiken:
- Wacht tot switch_1 is ingeschakeld en voeg een
start_event
toe voor alle LED's laag:
red_monitor_process : PROCESS BEGIN WAIT UNTIL reset = '0'; pulse_high(clk, led_low_start_event); WAIT UNTIL motor_start_in.switch_1 = '1';
- We gebruiken dezelfde FOR LOOP uit de eerste testcase voor het knipperen van de RED_LED en voegen een
start_event
toe voor RED_LED hoog:
-- 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;
- Nu wachten we tot switch_2 is ingeschakeld, en dan voegen we een
end_event
toe voor RED_LED hoog:
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;
- Nu voegen we de
check_stable
. toe procedures voor RED_LED hoog en laag:
-- 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"));
Laten we dit testen met de volgende testcase:
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 =====");
We voeren de simulatie als volgt uit:
python .\run.py *red_led* -v
Het resultaat is:
Opmerking dat de checker nu red_led_checker
. is .
We kunnen dezelfde benadering volgen om een zelfcontrolerende checker voor YELLOW_LED te ontwerpen, maar ik laat dit als een oefening voor de lezer. Ik zal echter de volgende verschillende manieren laten zien om de GREEN_LED-functionaliteit te verifiëren met behulp van check
, check_stable
, check_next
, en check_equal
procedures.
Om te controleren of GREEN_LED UIT is vanaf het begin tot de eerste keer dat switch_3 wordt ingeschakeld, gebruiken we gewoon de check_stable
procedure:
-- 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"));
Een manier om te controleren of GREEN_LED AAN is wanneer switch_3 is ingeschakeld, is door de check_next
te gebruiken procedure. We noemen het met switch_3 als de start_event
, wijs 1 klokcyclus toe aan num_cks
, en laat overlapping toe:
-- 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);
Omdat we overlapping hebben toegestaan, wordt deze procedure geactiveerd op elke stijgende flank van de klok wanneer switch_3 '1' is.t zal verwachten dat GREEN_LED na één klokcyclus '1' is, en dit is wat we willen.
Een andere manier om de GREEN_LED-functionaliteit te testen, is door de geklokte versie van de check
. te gebruiken procedure met een vertraagde versie van switch_3 als Enable voor deze procedure:
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
is een vertraagd signaal van 1 klokcyclus van switch_3. Het zal deze procedure dus altijd 1 klokcyclus inschakelen nadat switch_3 '1' is, en de procedure controleert of GREEN_LED '1' is wanneer deze is ingeschakeld.
De switch_3_delayed
signaal is een vertraagde versie switch_3 met één klokcyclus. Het zal deze procedure dus altijd inschakelen één klokcyclus nadat switch_3 '1' is. Indien ingeschakeld, controleert de procedure of GREEN_LED '1' is.
Opmerking: de EN NIET inschakelen in switch_3_delayed
is alleen om dit signaal te maskeren wanneer we de procesbenadering niet gebruiken.
Ten slotte kunnen we een speciaal proces gebruiken met een VHDL While-lus om de controle op GREEN_LED uit te voeren:
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;
Nu ontwikkelen we als volgt een testcase voor GREEN_LED:
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 =====");
Nadat u de testcase hebt geschreven, kunt u deze uitvoeren en de verschillende resultaten controleren.
We zien dat de testgevallen in de autocontrole-aanpak veel beter leesbaar zijn, zelfs voor technici zonder kennis van VHDL.
Er zijn nog andere testgevallen in het testbench-bestand. We kunnen ze starten met dit commando:
python .\run.py *motor_start_tb* --list
En dit is de uitvoer die naar de console is afgedrukt:
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
We kunnen alle testgevallen uitvoeren door te typen:
python .\run.py
En het resultaat is als volgt:
==== 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!
Samenvatting
Het VUnit-framework biedt geavanceerde functies voor automatisering van testruns en uitgebreide bibliotheken voor de ontwikkeling van testbanken. Bovendien kunnen ontwerpingenieurs hun ontwerp vroeg en vaak testen.
VUnit helpt verificatie-engineers ook om testbanken sneller en gemakkelijker te ontwikkelen en uit te voeren. Het belangrijkste is dat het een snelle en lichte leercurve heeft.
Waar te gaan vanaf hier
- VUnit-documentatie
- VUnit-blog
- VUnit gitter
Download het voorbeeldproject via onderstaand formulier!
VHDL
- Code Ready-containers:aan de slag met procesautomatiseringstools in de cloud
- Aan de slag met keramisch 3D-printen
- Maak kennis met basiskleurstoffen!
- Aan de slag met TJBot
- Aan de slag met de RAK 831 Lora Gateway en RPi3
- Aan de slag met de RAK831 LoRa Gateway en RPi3
- Aan de slag met IoT
- Aan de slag met AI in verzekeringen:een inleidende gids
- Arduino-zelfstudie 01:Aan de slag
- Aan de slag met My.Cat.com
- Node-RED en aan de slag met Docker