Premiers pas avec VUnit
VUnit est l'un des frameworks de vérification VHDL open source les plus populaires disponibles aujourd'hui. Il combine un exécuteur de suite de tests Python avec une bibliothèque VHDL dédiée pour automatiser vos bancs de test.
Pour vous donner ce tutoriel VUnit gratuit, VHDLwhiz fait appel à Ahmadmunthar Zaklouta, qui est derrière le reste de cet article, y compris le projet d'exemple simple VUnit que vous pouvez télécharger et exécuter sur votre ordinateur.
Passons le mot à Ahmad !
Ce didacticiel vise à démontrer l'utilisation du framework VUnit dans le processus de vérification de votre conception. Il vous guidera tout au long du processus de configuration de VUnit, de création du testbench VUnit, d'utilisation de la bibliothèque de vérification VUnit et d'exécution de tests VUnit dans ModelSim. Il présente également certaines techniques de vérification.
Aperçu
Cet article contient plusieurs captures d'écran. Cliquez sur les images pour les agrandir !
Utilisez la barre latérale pour naviguer dans le plan pour ce didacticiel, ou faites défiler vers le bas et cliquez sur le bouton de navigation contextuel dans le coin supérieur droit si vous utilisez un appareil mobile.
Exigences
Ce didacticiel suppose que ce logiciel est installé sur une machine Windows :
- Intel ModelSim
- Veuillez vous référer à cet article pour savoir comment installer ModelSim gratuitement.
- ModelSim devrait être dans votre PATH.
- Python 3.6 ou supérieur.
- Télécharger Python
- Python doit être dans votre PATH.
- GIT (facultatif).
- Télécharger GIT
- Terminal Windows (optionnel)
- Télécharger le terminal Windows
Cela suppose également d'avoir des connaissances de base en VHDL et des compétences ModelSim.
Installation
- Obtenir VUnit :
- Si vous avez GIT, vous pouvez le cloner de GitHub sur votre lecteur C :
git clone --recurse-submodules https://github.com/VUnit/vunit.git
- Sinon, vous pouvez le télécharger sous forme de zip depuis GitHub et l'extraire sur votre lecteur C :
- Téléchargez VUnit.
- Sinon, vous pouvez le télécharger sous forme de zip depuis GitHub et l'extraire sur votre lecteur C :
- Installation de VUnit :
- Ouvrez un terminal et accédez à
C:\vunit
et tapez la commande suivante :
- Ouvrez un terminal et accédez à
python setup.py install
- Configuration de VUnit :
- Ajoutez des variables d'environnement à votre système comme ci-dessous.
VUNIT_MODELSIM_PATH
:chemin d'accès à ModelSim sur votre machine.VUNIT_SIMULATOR
:ModelSim
Téléchargez l'exemple de projet
Vous pouvez télécharger l'exemple de projet et le code VHDL en utilisant le formulaire ci-dessous.
Extrayez le Zip vers C:\vunit_tutorial .
Présentation
VUnit est un cadre de test pour HDL qui facilite le processus de vérification en fournissant un flux de travail piloté par les tests "tester tôt et souvent" et une boîte à outils pour l'automatisation et l'administration des tests en cours. Il s'agit d'un cadre avancé avec de nombreuses fonctionnalités riches, mais pourtant facile à utiliser et à adapter. Il est entièrement open source et peut être facilement intégré aux méthodologies de test traditionnelles.
VUnit se compose de deux composants principaux :
- Bibliothèque Python : fournit des outils qui facilitent l'automatisation, l'administration et la configuration des tests.
- Bibliothèque VHDL : un ensemble de bibliothèques qui facilitent les tâches de vérification courantes.
La partie VHDL se compose de six bibliothèques, comme indiqué dans le schéma ci-dessous. Ce didacticiel utilisera les bibliothèques de journalisation et de vérification.
Conception en cours de test
Le design utilisé dans ce tutoriel, nommé motor_start
, implémente une procédure de démarrage pour un moteur particulier et pilote 3 LED représentant l'état du moteur.
L'interface se compose d'un enregistrement d'entrée motor_start_in
avec 3 éléments (3 interrupteurs en entrée) et un enregistrement de sortie motor_start_out
avec 3 éléments (3 LED ROUGE, JAUNE, VERT comme sorties).
Certains moteurs doivent être initialisés au début avant de pouvoir commencer à les utiliser. Notre procédure de démarrage du moteur comporte trois étapes :
- Configuration du chargement
- Étalonnage
- Rotation
Séquence de démarrage
Voici les séquences de démarrage du moteur et la signification des indicateurs LED.
- RED_LED représente la configuration de chargement .
- Activez switch_1.
- Le RED_LED commencera à clignoter 5 fois.
- Attendez que le RED_LED cesse de clignoter et reste allumé en permanence.
- YELLOW_LED représente l'calibrage de chargement .
- Vous pouvez maintenant activer switch_2.
- Lorsque le switch_2 est activé, YELLOW_LED commencera à s'allumer en permanence après 5 cycles d'horloge, durant 10 cycles d'horloge. Et après cela, YELLOW_LED et RED_LED s'éteindront.
- LED_VERTE indique que le moteur tourne.
- Maintenant, le moteur est prêt à être utilisé. Chaque fois que switch_3 est allumé, GREEN_LED commencera à s'allumer constamment jusqu'à ce que switch_3 soit éteint.
- Toute violation de cette séquence entraînera l'allumage constant des 3 LED jusqu'à ce que tous les interrupteurs soient éteints, et la séquence pourra être relancée.>
Développement d'un banc d'essai
Cette partie du didacticiel comprend les sous-sections suivantes :
- Configuration du script d'exécution Python
- Configuration du squelette VUnit
- Configuration du banc d'essai
Configuration du script d'exécution Python run.py
Chaque projet VUnit a besoin d'un script python de haut niveau run.py
qui sert de point d'entrée au projet et spécifie toutes les sources de conception, de banc d'essai et de bibliothèque VHDL.
Ce fichier existe généralement dans le répertoire du projet. L'arborescence de répertoires utilisée dans ce didacticiel est la suivante :
Dans le run.py
file, nous devons faire trois choses :
1 - Récupérez le chemin où ce fichier existe et spécifiez le chemin pour les fichiers de conception et de banc d'essai.
# ROOT ROOT = Path(__file__).resolve().parent # Sources path for DUT DUT_PATH = ROOT / "design" # Sources path for TB TEST_PATH = ROOT / "testbench"
2 – Créez l'instance VUnit.
Cela créera une instance VUnit et l'affectera à une variable nommée VU
. Ensuite, nous pouvons utiliser VU
pour créer des bibliothèques et diverses tâches.
# create VUnit instance VU = VUnit.from_argv() VU.enable_location_preprocessing()
3 – Créez des bibliothèques et ajoutez-y des sources VHDL.
J'aime bien séparer la partie design de la partie testbench. Par conséquent, nous allons créer deux bibliothèques :design_lib
et 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"])
Le reste du fichier est une configuration pour ModelSim pour utiliser le wave.do
fichier s'il existe.
Remarque : ici, j'utilise le *.vhdl
extension. Vous devrez peut-être le modifier si vous utilisez *.vhd
.
Si vous aimez cette structure de travail, vous n'avez pas du tout besoin de modifier ce fichier. Chaque fois que vous démarrez un nouveau projet, copiez-le simplement dans votre répertoire de projet. Cependant, si vous préférez une structure de répertoires différente, vous devez modifier les chemins pour vous conformer à votre structure de travail.
Désormais, chaque fois que nous utiliserons ce fichier, VUnit recherchera automatiquement les bancs de test VUnit dans votre projet, déterminera l'ordre de compilation, créera les bibliothèques et compilera les sources dans celles-ci, et exécutera éventuellement le simulateur avec tous les cas de test ou des cas de test spécifiques.
N'est-ce pas génial? 😀
Configuration du squelette VUnit
Pour créer un banc de test VUnit, nous devons ajouter du code spécifique à notre fichier de banc de test motor_start_tb
, comme expliqué dans cette sous-section.
1 – Ajoutez les bibliothèques comme suit.
Tout d'abord, nous devons ajouter la bibliothèque VUnit VUNIT_LIB
et son contexte :VUNIT_CONTEXT
, afin que nous ayons accès à la fonctionnalité VUnit comme suit :
LIBRARY VUNIT_LIB; CONTEXT VUNIT_LIB.VUNIT_CONTEXT;
Deuxièmement, nous devons ajouter les bibliothèques de conception et de banc d'essai DESIGN_LIB
et TB_LIB
afin que nous ayons accès à nos DUT et packages comme suit :
LIBRARY DESIGN_LIB; USE DESIGN_LIB.MOTOR_PKG.ALL; LIBRARY TB_LIB; USE TB_LIB.MOTOR_TB_PKG.ALL;
Le DUT comporte deux packages; un pour la conception :motor_pkg
et l'autre pour les éléments de testbench motor_tb_pkg
. Ce sont des packages triviaux que j'ai créés car c'est généralement ainsi que les grands projets sont structurés. Je veux montrer comment VUnit gère cela.
motor_start
etmotor_pkg
sera compilé enDESIGN_LIB
.motor_start_tb
etmotor_tb_pkg
sera compilé enTB_LIB
.
2 – Ajoutez la configuration de l'exécuteur à l'entité comme suit :
ENTITY motor_start_tb IS GENERIC(runner_cfg : string := runner_cfg_default); END ENTITY motor_start_tb;
runner_cfg
est une constante générique qui permet au testeur python de contrôler le testbench. Autrement dit, nous pouvons exécuter des tests à partir de l'environnement python. Ce générique est obligatoire et ne change pas.
3 – Ajoutez le squelette du banc de test VUnit à notre banc de test comme suit :
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
est le principal processus de contrôle du banc d'essai. Il commence toujours par la procédure test_runner_setup
et termine avec la procédure test_runner_cleanup
. La simulation vit entre ces deux procédures. test_cases_loop
est nos combinaisons de test où tous nos cas de test ont lieu.
Pour créer un cas de test, nous utilisons le run
de VUnit fonction dans une instruction If comme suit :
IF run("test_case_name") THEN -- test case code here ELSIF run("test_case_name") THEN -- test case code here END IF;
Ensuite, nous pouvons exécuter tous les cas de test ou des cas de test spécifiques à partir de l'environnement Python en les appelant simplement avec le nom que nous avons spécifié dans l'appel à run
.
Configuration du banc d'essai
Ici, nous commençons par ajouter les signaux requis pour communiquer avec le DUT, comme indiqué ci-dessous :
-------------------------------------------------------------------------- -- 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');
Remarque : Il est recommandé d'initialiser les signaux avec une valeur initiale.
Ensuite, instanciez le DUT comme ceci :
-------------------------------------------------------------------------- -- 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 );
Remarque : J'ai regroupé les ports d'entrée et les ports de sortie dans des enregistrements. Je trouve cela bénéfique dans les grands projets car cela rend les entités et les instanciations moins encombrées.
Et enfin, conduisez clk
, reset
, et led_out
comme indiqué ici :
-------------------------------------------------------------------------- -- 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);
Développement de cas de test
Revenons maintenant à notre DUT et commençons le travail proprement dit en développant quelques cas de test. Je vais présenter deux scénarios :
Scénario de l'ingénieur concepteur : du point de vue du concepteur, le concepteur effectuant lui-même la vérification. Dans ce scénario, qui se produit généralement pendant la phase de développement, le concepteur peut accéder au code. Ce scénario montrera comment VUnit nous aide à "tester tôt et souvent".
Scénario de l'ingénieur de vérification :la conception (DUT) est traitée comme une boîte noire. Nous ne connaissons que l'interface externe et les exigences de test.
Nous examinerons également ces trois techniques de vérification :
- Pilote et vérificateur dans le scénario de test.
- Pilote et vérificateur contrôlé dans le cas de test.
- Pilote dans le scénario de test et vérificateur d'auto-vérification.
Commençons par la première technique et revenons aux deux dernières méthodes plus loin dans cet article.
Pilote et vérificateur dans le scénario de test
C'est l'approche la plus directe. Le conducteur et le vérificateur sont à l'intérieur du cas de test lui-même, nous implémentons les opérations de conduite et de vérification dans le code du cas de test.
Supposons que nous ayons développé la fonctionnalité RED_LED comme ci-dessous :
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;
Et maintenant, nous voulons vérifier notre conception avant de continuer à développer le reste de la fonctionnalité.
Pour ce faire, nous utilisons le run
de VUnit fonction à l'intérieur du test_suite
pour créer un scénario de test pour vérifier la sortie de l'activation de switch_1, comme indiqué ci-dessous :
IF run("switch_1_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switches_off_output_check"); info("------------------------------------------------------------------"); -- code for your test case here.
Cela créera un cas de test nommé "switch_1_on_output_check"
Remarque : info
est une procédure VUnit de la bibliothèque de journalisation qui s'imprime sur l'écran des transcriptions et le terminal. Nous l'utiliserons pour afficher le résultat du test.
Maintenant, nous allons écrire le code de ce cas de test. Nous utiliserons les sous-programmes de vérification de VUnit pour ce faire.
Nous savons que RED_LED clignotera 5 fois après l'activation de switch_1, nous créons donc une boucle VHDL For et effectuons la vérification à l'intérieur.
Le check
procédure effectue un contrôle sur les paramètres spécifiques que nous fournissons. Il a de nombreuses variantes, et ici, j'en ai utilisé plusieurs à des fins de démonstration.
check(expr => motor_start_out.red_led = '1', msg => "Expect red_led to be ON '1'");
Ici, il testera si RED_LED est '1' à ce moment du temps de simulation et imprimera un message à la console :
# 35001 ps - check - PASS - red_led when switch_1 on (motor_start_tb.vhdl:192)
Remarque qu'il nous indique s'il s'agit d'un PASS ou d'une ERREUR, et l'horodatage du moment où cette vérification s'est produite, ainsi que le nom du fichier et le numéro de ligne où se trouve cette vérification.
Une autre façon est d'utiliser le check_false
procédure. Ici, nous l'utilisons pour vérifier que YELLOW_LED vaut '0' :
check_false(expr => ??motor_start_out.yellow_led, msg => result("for yellow_led when switch_1 on"));
Ici, nous utilisons le result
de VUnit fonction pour améliorer le message. L'impression ressemblera à ceci :
# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on # (motor_start_tb.vhdl:193)
Remarque qu'il imprime des informations supplémentaires sur le type de vérification :"Faux contrôle réussi".
Encore une autre façon est d'utiliser check_equal
. Ici, nous l'utilisons pour tester que GREEN_LED est '0' :
check_equal(got => motor_start_out.green_led, expected => '0', msg => result("for green_led when switch_1 on"));
Ici, nous avons fourni un paramètre supplémentaire "0" pour la comparaison. L'impression résultante est :
# 35001 ps - check - PASS - Equality check passed for green_led when switch_1 on - # Got 0. (motor_start_tb.vhdl:194)
Maintenant, nous savons qu'après un cycle d'horloge, RED_LED s'éteindra et les autres LED resteront également éteintes. Nous pouvons utiliser check_equal
pour les vérifier tous simultanément, comme indiqué ci-dessous :
check_equal(led_out, STD_LOGIC_VECTOR'("000"), result("for led_out when switch_1 on"), warning);
Remarque l'utilisation du qualificatif STD_LOGIC_VECTOR'("000")
, les valeurs ne sont donc pas ambiguës pour la procédure. De plus, nous avons spécifié que le niveau de cette vérification était AVERTISSEMENT, ce qui signifie que si cette vérification échoue, elle émettra un avertissement au lieu de générer une erreur. La sortie ressemblera à ceci :
# 45001 ps - check - PASS - Equality check passed for led_out when switch_1 on - # Got 000 (0). (motor_start_tb.vhdl:197)
Voici le code du scénario de test complet :
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 =====");
Cas de test en cours d'exécution
Il est maintenant temps d'exécuter notre cas de test. Nous pouvons exécuter les tests dans le terminal ou dans l'interface graphique du simulateur.
Exécuter un test dans le terminal
Pour ce faire, commencez par ouvrir un nouveau terminal (CMD, PowerShell, Windows Terminal) et accédez au vunit_tutorial répertoire où se trouve le run.py
le fichier est localisé.
Pour exécuter le scénario de test, tapez simplement :
python .\run.py *switch_1_on_output_check
VUnit compilera tous les fichiers VHDL dans le bon ordre et les analysera, à la recherche de bancs de test VUnit. Et ensuite, VUnit regardera à l'intérieur de ces fichiers, recherchant un run
fonction avec le nom de cas de test "switch_1_on_output_check" pour l'exécuter.
Remarque : nous plaçons le symbole générique * avant le cas de test pour correspondre à son nom complet, qui est :
tb_lib.motor_start_tb.switch_1_on_output_check
Nous pouvons le faire car l'interface de ligne de commande de VUnit accepte les caractères génériques.
L'impression résultante après simulation est :
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!
Nous pouvons voir qu'un test a été exécuté et qu'il a été RÉUSSI.
Remarque que VUnit a créé un vunit_out dossier dans le répertoire du projet. Dans ce dossier, il y a un dossier nommé test_output qui a des rapports sur les tests.
Ci-dessus, nous n'avons obtenu que le résultat final du test sans détails sur chaque vérification, mais l'outil de ligne de commande VUnit fournit plusieurs commutateurs pour l'exécution des tests. Pour obtenir plus d'informations sur ce qui se passe pendant la simulation, nous pouvons utiliser le commutateur verbeux -v
:
python .\run.py *switch_1_on_output_check -v
L'impression détaillée ressemblera à ceci :
D'autres commutateurs utiles sont :
-l, --list
:Répertorier tous les cas de test.
--clean
:Supprimez d'abord le dossier de sortie, puis exécutez le test.
--compile
:Ce commutateur est utile si vous souhaitez compiler sans exécuter de tests pour vérifier les erreurs, par exemple.
Exécution d'un test dans l'interface graphique du simulateur
Souvent, une inspection visuelle de la vague est nécessaire. VUnit fournit un moyen automatisé d'exécuter des tests dans le simulateur en utilisant le commutateur GUI -g
. VUnit fera toute la compilation et lancera ModelSim (le simulateur que nous avons configuré) avec le cas de test demandé et ajoutera les signaux à la fenêtre d'onde, étant donné qu'un wave.do
le fichier est disponible.
python .\run.py *switch_1_on_output_check -g
Maintenant, VUnit va lancer ModelSim pour ce cas de test spécifique, ouvrir la fenêtre de forme d'onde et ajouter les signaux car j'ai déjà le motor_start_tb_wave.do
à l'intérieur des vagues dossier.
Remarque : Vous pouvez personnaliser la forme d'onde comme vous le souhaitez, mais vous devez enregistrer le fichier de format de forme d'onde dans les waves dossier avec cette convention de nom testbench_file_name_wave.do
afin qu'il puisse être chargé lorsque VUnit lance ModelSim.
Supposons que vous modifiiez votre code après avoir découvert des erreurs et que vous souhaitiez réexécuter ce cas de test. Dans ce cas, vous pouvez taper dans la fenêtre de transcription de ModelSim :vunit_restart
, Cela obligera VUnit à recompiler, redémarrer et relancer la simulation.
Pilote et vérificateur contrôlé dans le cas de test
Jusqu'à présent, nous avons appris à configurer un banc de test VUnit et à développer et exécuter le cas de test. Dans cette section, nous développerons plus de cas de test du point de vue de l'ingénieur de vérification, en utilisant une approche de vérification différente et une bibliothèque de vérificateurs VUnit.
Contrairement au cas de test précédent, cette approche a le pilote à l'intérieur du cas de test et le vérificateur à l'extérieur, mais le cas de test le contrôle toujours.
Supposons que nous ayons cette exigence de vérification :
- Vérifiez la sortie après avoir allumé l'interrupteur_2 alors que l'interrupteur_1 est allumé et que la LED ROUGE est allumée.
De la section DUT, nous savons que :
- Lorsque switch_2 est allumé, YELLOW_LED commencera à s'allumer constamment après 5 cycles d'horloge pendant 10 cycles d'horloge, et après cela, YELLOW_LED et RED_LED s'éteindront.
Nous utiliserons le check_stable
de VUnit procédure pour vérifier que :
- YELLOW_LED est '0' depuis le début jusqu'à ce que switch_2 soit allumé.
- YELLOW_LED est '1' pour 10 cycles d'horloge.
Nous utiliserons le check_next
de VUnit procédure pour vérifier que :
- YELLOW_LED est '1' après 5 cycles d'horloge à partir de la mise en marche du switch_2.
check_stable :vérifie que le ou les signaux sont stables dans une fenêtre qui commence par un start_event
impulsion de signal et se termine par un end_event
signal d'impulsion.
check_next :vérifier que signal ='1' après un certain nombre de cycles d'horloge après un start_event
signal d'impulsion.
start_event
et end_event
les signaux seront contrôlés à partir du scénario de test.
Nous commençons par ajouter les signaux requis pour check_stable
et check_next
procédures comme suit :
-- 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';
Ensuite, nous créons un nouveau cas de test dans le test_suite
en utilisant le run
de VUnit fonctionnent comme suit :
ELSIF run("switch_2_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switch_2_on_output_check"); info("------------------------------------------------------------------");
Nous créons un start_event
pour l'état bas de YELLOW_LED à utiliser avec le check_stable
procédure comme suit :
WAIT UNTIL reset <= '0'; -- out of reset enable <= '1'; pulse_high(clk, yellow_low_start_event); WAIT FOR C_CLK_PERIOD * 3;
Le enable
le signal active le check_stable
et check_next
procédures, et nous voulons les activer dès le début.
pulse_high
est une procédure triviale de motor_tb_pkg
qui attend le prochain front d'horloge montant et envoie un signal pendant un cycle d'horloge. Dans ce cas, il s'agit de yellow_ low_start_event
.
Maintenant, nous allumons le switch_1 et attendons que la LED RED_LED s'allume constamment, puis nous allumons le switch_2 :
-- 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';
Maintenant, nous savons qu'après 5 cycles d'horloge, YELLOW_LED sera "1". Par conséquent, nous créons un start_event
pour YELLOW_LED à utiliser avec le check_next
procédure :
-- 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
De même, nous créons un end_event
pour l'état bas de YELLOW_LED à utiliser avec le check_stable
procédure :
WAIT FOR C_CLK_PERIOD * 3; -- end event for YELLOW_LED low pulse_high(clk, yellow_low_end_event); -- 5th clk cycle
Maintenant, YELLOW_LED sera haut pendant 10 cycles d'horloge. Par conséquent, nous créons un start_event
pour l'état haut de YELLOW_LED pour le check_stable
procédure :
-- 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';
Ici, je n'ai pas utilisé le pulse_high
procédure parce que je veux que l'impulsion se produise maintenant, et non dans le prochain front descendant.
Et nous créons un end_event
pour l'état haut de YELLOW_LED pour check_stable
après 8 cycles d'horloge comme suit :
WAIT FOR C_CLK_PERIOD * 8; -- end event for YELLOW_LED pulse_high(clk, yellow_high_end_event); -- 10th clk cycle
Avec cela, le cas de test est terminé. Nous n'avons qu'à ajouter les appels aux procédures de vérification après le processus comme ceci :
---------------------------------------------------------------------- -- 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"));
Remarque : le allow_restart
paramètre dans le check_stable
procédure permet à une nouvelle fenêtre de démarrer si un nouveau start_event
se produit avant le end_event
.
Remarque : on met 5 dans check_next
procédure car YELLOW_LED sera haute après 5 cycles d'horloge de yellow_next_start_event
.
Remarque : les deux derniers paramètres en check_next
sont :
allow_overlapping
:autoriser un secondstart_event
avant l'expr pour le premier est '1'.allow_missing_start
:permet à expr d'être '1' sansstart_event
.
Maintenant, nous pouvons exécuter le scénario de test à partir de la ligne de commande comme ceci :
python .\run.py *switch_2_on_output_check -v
Et le résultat sera le suivant :
Nous pouvons exécuter le scénario de test dans l'interface graphique du simulateur comme ceci :
python .\run.py *switch_2_on_output_check -g
Résultant en cette forme d'onde :
Remarque :start_event
et end_event
signaux pour check_stable
sont inclus dans la fenêtre, et le ou les signaux surveillés sont référencés lorsque start_event='1'
.
Dans ce cas de test, nous avons retiré les opérations de vérification du cas de test mais les avons contrôlées à partir du cas de test. Notez également que nous n'avons pas vérifié la fonctionnalité RED_LED.
Pilote dans le scénario de test et vérificateur d'auto-vérification
Un inconvénient de l'approche précédente est qu'elle est moins lisible. Le cas de test contient des informations qui ne sont pas intéressantes ou liées au test ou à la fonctionnalité, telles que le start_event
et end_event
signaux. Nous voulons masquer tous ces détails, n'avoir que le pilote dans le cas de test et créer un vérificateur d'auto-vérification.
Commençons par concevoir le pilote.
Les procédures de VHDL sont un bon candidat pour cela. Si vous ne savez pas comment utiliser une procédure VHDL, consultez ce tutoriel.
Ci-dessous la procédure switch_driver
de 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;
Nous fournissons la période d'horloge pour calculer les retards et un entier qui spécifie le retard souhaité pour chaque commutateur en périodes d'horloge.
- Les valeurs naturelles (>=0) signifie :allumer l'interrupteur après (
clk_period
*sw_delay
). - La valeur -1 signifie :éteignez l'interrupteur.
- Toutes les autres valeurs négatives signifient :ne rien faire.
Nous pouvons maintenant utiliser cette procédure dans le cas de test comme suit :
switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);
Cela allumera le switch_1 après 3 cycles d'horloge, puis allumera le switch_2 après 12 cycles d'horloge et, enfin, allumera le switch_3 après 20 cycles d'horloge.
Jusqu'à présent, nous avons utilisé le vérificateur par défaut de VUnit. VUnit offre la possibilité d'avoir un vérificateur personnalisé. Ceci est utile lorsque vous avez un gros cas de test avec beaucoup de vérifications, et que vous voulez avoir des statistiques sur la vérification, ou que vous ne voulez pas confondre la vérification avec le reste du banc de test.
Nous allons créer un vérificateur personnalisé pour RED_LED et définir le niveau de journalisation des échecs sur AVERTISSEMENT :
CONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);
Et nous activons la journalisation des vérifications de réussite pour RED_CHECKER dans la section Setup VUnit :
show(get_logger(RED_CHECKER), display_handler, pass);
Passons maintenant au vérificateur d'auto-vérification (ou moniteur). Nous allons d'abord concevoir un vérificateur d'auto-vérification pour RED_LED, et nous utiliserons un processus pour cela comme suit :
- Attendez que switch_1 soit activé et ajoutez un
start_event
pour toutes les LED faible :
red_monitor_process : PROCESS BEGIN WAIT UNTIL reset = '0'; pulse_high(clk, led_low_start_event); WAIT UNTIL motor_start_in.switch_1 = '1';
- Nous utilisons la même BOUCLE FOR du premier cas de test pour le clignotement de RED_LED et ajoutons un
start_event
pour RED_LED élevé :
-- 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;
- Nous attendons maintenant que switch_2 soit activé, puis nous ajoutons un
end_event
pour RED_LED élevé :
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;
- Nous ajoutons maintenant le
check_stable
procédures pour RED_LED haut et bas :
-- 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"));
Testons cela avec le cas de test suivant :
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 =====");
Nous exécutons la simulation comme ceci :
python .\run.py *red_led* -v
Le résultat sera :
Remarque que le vérificateur est maintenant red_led_checker
.
Nous pouvons suivre la même approche pour concevoir un vérificateur d'auto-vérification pour YELLOW_LED, mais je laisserai cela comme un exercice pour le lecteur. Cependant, je vais montrer les différentes façons suivantes de vérifier la fonctionnalité GREEN_LED en utilisant check
, check_stable
, check_next
, et check_equal
procédures.
Pour vérifier que GREEN_LED est éteint depuis le début jusqu'à la première fois que switch_3 est allumé, nous utilisons simplement le check_stable
procédure :
-- 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"));
Une façon de vérifier que GREEN_LED est allumé lorsque switch_3 est allumé utilise le check_next
procédure. Nous l'appelons avec switch_3 comme start_event
, attribuez 1 cycle d'horloge à num_cks
, et autorisez le chevauchement :
-- 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);
Parce que nous avons autorisé le chevauchement, cette procédure sera déclenchée à chaque front montant de l'horloge lorsque switch_3 est '1'.t s'attendra à ce que GREEN_LED soit '1' après un cycle d'horloge, et c'est ce que nous voulons.
Une autre façon de tester la fonctionnalité GREEN_LED consiste à utiliser la version cadencée du check
procédure avec une version retardée de switch_3 comme Activer pour cette procédure :
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
est un signal retardé d'un cycle d'horloge de switch_3. Ainsi, il activera cette procédure toujours 1 cycle d'horloge après que switch_3 est '1', et la procédure vérifie que GREEN_LED est '1' lorsqu'il est activé.
Le switch_3_delayed
Le signal est une version switch_3 retardée d'un cycle d'horloge. Ainsi, il activera cette procédure toujours un cycle d'horloge après que switch_3 soit '1'. Lorsqu'elle est activée, la procédure vérifie que GREEN_LED est '1'.
Remarque : le AND NOT activé dans switch_3_delayed
est juste de masquer ce signal lorsque nous n'utilisons pas l'approche processus.
Enfin, nous pouvons utiliser un processus dédié avec une boucle While VHDL pour effectuer la vérification de GREEN_LED :
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;
Nous développons maintenant un cas de test pour GREEN_LED comme suit :
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 =====");
Après avoir écrit le scénario de test, vous pouvez l'exécuter et vérifier les différents résultats.
Nous pouvons voir que les cas de test dans l'approche d'auto-vérification sont beaucoup plus lisibles, même pour les ingénieurs sans connaissances en VHDL.
Il existe d'autres cas de test dans le fichier testbench. Nous pouvons les démarrer en utilisant cette commande :
python .\run.py *motor_start_tb* --list
Et voici la sortie imprimée sur la console :
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
Nous pouvons exécuter tous les cas de test en tapant :
python .\run.py
Et le résultat est le suivant :
==== 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!
Résumé
Le framework VUnit fournit des fonctionnalités avancées pour l'automatisation de l'exécution des tests et des bibliothèques riches pour le développement de bancs d'essai. De plus, cela permet aux ingénieurs de conception de tester leur conception tôt et souvent.
VUnit aide également les ingénieurs de vérification à développer et à exécuter des bancs de test plus rapidement et plus facilement. Plus important encore, il a une courbe d'apprentissage rapide et légère.
Où aller à partir d'ici
- Documentation VUnit
- Blog VUnit
- Gitter VUnit
Téléchargez l'exemple de projet en utilisant le formulaire ci-dessous !
VHDL
- Conteneurs Code Ready :Premiers pas avec les outils d'automatisation des processus dans le cloud
- Initiation à l'impression 3D céramique
- Se familiariser avec les teintures de base !
- Premiers pas avec TJBot
- Premiers pas avec le RAK 831 Lora Gateway et RPi3
- Premiers pas avec la passerelle LoRa RAK831 et RPi3
- Commencer à travailler avec l'IoT
- Premiers pas avec l'IA dans l'assurance :un guide d'introduction
- Tutoriel Arduino 01 :Prise en main