Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial programming >> VHDL

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 :

Cela suppose également d'avoir des connaissances de base en VHDL et des compétences ModelSim.

Installation

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

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 :

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 :

  1. Configuration du chargement
  2. Étalonnage
  3. Rotation

Séquence de démarrage

Voici les séquences de démarrage du moteur et la signification des indicateurs LED.

  1. Activez switch_1.
  2. Le RED_LED commencera à clignoter 5 fois.
  3. Attendez que le RED_LED cesse de clignoter et reste allumé en permanence.
  1. Vous pouvez maintenant activer switch_2.
  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.
  1. 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.
  2. 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 :

  1. Configuration du script d'exécution Python
  2. Configuration du squelette VUnit
  3. 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.

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 :

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 :

De la section DUT, nous savons que :

Nous utiliserons le check_stable de VUnit procédure pour vérifier que :

Nous utiliserons le check_next de VUnit procédure pour vérifier que :

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 :

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.

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 :

red_monitor_process : PROCESS
BEGIN
  WAIT UNTIL reset = '0';
  pulse_high(clk, led_low_start_event);
  WAIT UNTIL motor_start_in.switch_1 = '1';
-- RED_LED is blinking
FOR i IN 0 TO 4 LOOP
  IF (motor_start_in.switch_1 = '1' AND
      motor_start_in.switch_2 = '0' AND
      motor_start_in.switch_3 = '0') THEN

    WAIT UNTIL rising_edge(clk);
    WAIT FOR 1 ps;
    check(RED_CHECKER, motor_start_out.red_led = '1', 
          result("for red_led blink high"));

    WAIT UNTIL rising_edge(clk);
    WAIT FOR 1 ps;
    check(RED_CHECKER, motor_start_out.red_led = '0',
          result("for red_led blink low"));

    -- RED_LED is constantly lighting start event
    IF (i = 4) THEN -- finish blinking
      WAIT UNTIL rising_edge(clk);
      WAIT FOR 1 ps;
      check(RED_CHECKER, motor_start_out.red_led = '1',
            result("for red_led blink low"));
      pulse_high(clk, red_high_start_event);
    END IF;
  ELSE
  -- perform check for error (All led high until all switches are off)
  END IF;
END LOOP;
  IF (motor_start_in.switch_2 /= '1') THEN
    WAIT UNTIL motor_start_in.switch_2 = '1';
  END IF;
  WAIT UNTIL rising_edge(clk);
  WAIT FOR C_CLK_PERIOD * 14;
  -- RED_LED is constantly lighting end event
  pulse_high(clk, red_high_end_event);
END PROCESS red_monitor_process;
-- check that RED_LED is low from start until switch_1 is ON
-- Note the use of motor_start_in.switch_1 as end_event
check_stable(RED_CHECKER, clk, enable, led_low_start_event, 
             motor_start_in.switch_1, motor_start_out.red_led,
             result("RED_LED low before switch_1"));

-- check that RED_LED is low after switch_2 is ON
check_stable(RED_CHECKER, clk, enable, red_high_start_event, 
             red_high_end_event, motor_start_out.red_led,
             result("RED_LED high after switch_1"));

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

Téléchargez l'exemple de projet en utilisant le formulaire ci-dessous !


VHDL

  1. Conteneurs Code Ready :Premiers pas avec les outils d'automatisation des processus dans le cloud
  2. Initiation à l'impression 3D céramique
  3. Se familiariser avec les teintures de base !
  4. Premiers pas avec TJBot
  5. Premiers pas avec le RAK 831 Lora Gateway et RPi3
  6. Premiers pas avec la passerelle LoRa RAK831 et RPi3
  7. Commencer à travailler avec l'IoT
  8. Premiers pas avec l'IA dans l'assurance :un guide d'introduction
  9. Tutoriel Arduino 01 :Prise en main