Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial programming >> VHDL

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.
  • VUnit installeren:
    • Open een terminal en ga naar C:\vunit en typ het volgende commando:
 
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:

  1. Laadconfiguratie
  2. Kalibratie
  3. Rotatie

Opstartvolgorde

Hier volgt de opstartvolgorde van de motor en de betekenis van de LED-indicatoren.

  • RED_LED staat voor laadconfiguratie .
  1. Schakel schakelaar_1 in.
  2. De RODE_LED begint 5 keer te knipperen.
  3. Wacht tot de RED_LED stopt met knipperen en constant brandt.
  • GELE_LED staat voor laadkalibratie .
  1. Je kunt nu switch_2 inschakelen.
  2. 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.
  1. 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.
  2. 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:

  1. Het lopende script voor Python instellen
  2. Het VUnit-skelet opzetten
  3. 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 en motor_pkg wordt gecompileerd tot DESIGN_LIB .
  • motor_start_tb en motor_tb_pkg wordt gecompileerd tot TB_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 tweede start_event toe voor de expr voor de eerste is '1'.
  • allow_missing_start :laat expr '1' zijn zonder een start_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

  1. Code Ready-containers:aan de slag met procesautomatiseringstools in de cloud
  2. Aan de slag met keramisch 3D-printen
  3. Maak kennis met basiskleurstoffen!
  4. Aan de slag met TJBot
  5. Aan de slag met de RAK 831 Lora Gateway en RPi3
  6. Aan de slag met de RAK831 LoRa Gateway en RPi3
  7. Aan de slag met IoT
  8. Aan de slag met AI in verzekeringen:een inleidende gids
  9. Arduino-zelfstudie 01:Aan de slag
  10. Aan de slag met My.Cat.com
  11. Node-RED en aan de slag met Docker