Chapitre 13 — Bancs de test VHDL
Objectif — Finalité du chapitre Apprendre à construire des bancs de test structurés, reproductibles et auto-vérifiants afin de valider les circuits VHDL avant leur synthèse et leur implantation sur FPGA. |
Objectifs pédagogiques
À la fin de ce chapitre, l’étudiant devra être capable de :
- expliquer le rôle d’un banc de test ;
- créer une entité de test sans port ;
- instancier le composant à vérifier ;
- générer une horloge de simulation ;
- produire des stimuli temporels ;
- utiliser wait for et wait until ;
- comparer les sorties obtenues aux sorties attendues ;
- écrire des assertions avec différents niveaux de sévérité ;
- arrêter automatiquement une simulation ;
- organiser des tests nominaux, limites et invalides ;
- réaliser des tests exhaustifs avec des boucles ;
- analyser les chronogrammes et les messages du simulateur.
Prérequis
- entités, architectures et instanciations directes ;
- processus et instructions séquentielles ;
- types std_logic, std_logic_vector et unsigned ;
- horloges, bascules, registres et compteurs ;
- assertions élémentaires rencontrées dans les chapitres précédents.
Organisation du chapitre
Section | Contenu | Compétence principale |
|---|---|---|
| 13.1 | Rôle d’un testbench | Comprendre la stratégie de validation |
| 13.2 | Structure générale | Assembler l’environnement de test |
| 13.3 | Génération de l’horloge | Créer une base de temps fiable |
| 13.4 | Génération des stimuli | Reproduire des scénarios |
| 13.5 | Assertions | Détecter et signaler les erreurs |
| 13.6 | Tests automatisés | Construire une vérification auto-contrôlée |
| TP 10 | Multiplexeur, additionneur et compteur | Valider plusieurs familles de circuits |
Concept clé — Le testbench n’est pas le circuit Le banc de test est un environnement de simulation. Il peut utiliser des délais, des instructions wait, des fichiers et des fonctions de diagnostic qui ne sont pas destinés à la synthèse. |
13.1 Rôle d’un testbench
Un banc de test, ou testbench, applique des entrées à un circuit, observe les sorties et vérifie que le comportement obtenu correspond aux spécifications.
13.1.1 Validation fonctionnelle
La validation fonctionnelle consiste à vérifier les relations logiques et les séquences temporelles sans tenir compte, dans un premier temps, des délais physiques issus du placement et du routage.
- vérification des tables de vérité ;
- vérification des priorités ;
- vérification des transitions d’état ;
- vérification des resets et validations ;
- vérification des retours à zéro ;
- vérification des valeurs limites.
13.1.2 Détection des erreurs
Erreur détectable | Exemple |
|---|---|
| Erreur logique | XOR utilisé à la place de OR |
| Erreur de priorité | load traité après enable |
| Erreur de taille | Retenue supprimée |
| Erreur temporelle RTL | Sortie vérifiée avant le cycle delta |
| Erreur de reset | Sortie non réinitialisée |
| Erreur de séquence | Compteur modulo 10 atteignant 10 |
13.1.3 Reproduction de scénarios
Un testbench permet de rejouer exactement la même séquence de test. Cette répétabilité facilite la correction d’un défaut et évite les essais manuels non documentés.
| Scénario reproductible | VHDL |
| reset <= '1'; wait for 40 ns; reset <= '0'; enable <= '1'; wait for 100 ns; enable <= '0'; wait for 20 ns; |
13.1.4 Vérification avant synthèse
La simulation RTL doit être réalisée avant la synthèse afin de détecter rapidement les erreurs fonctionnelles. Corriger une erreur dans le code source est plus simple que l’identifier uniquement sur la carte.
Étape | Question principale |
|---|---|
| Analyse VHDL | Le code est-il syntaxiquement et sémantiquement valide ? |
| Simulation RTL | Le comportement fonctionnel est-il correct ? |
| Synthèse | Quel matériel est inféré ? |
| Simulation post-synthèse éventuelle | Le réseau synthétisé reste-t-il cohérent ? |
| Analyse temporelle | Les contraintes sont-elles respectées ? |
| Validation FPGA | Le système réel fonctionne-t-il dans son environnement ? |
13.1.5 Test manuel et test automatique
Test manuel | Test automatique |
|---|---|
| Observation visuelle du chronogramme | Assertions et modèle de référence |
| Adapté à l’exploration | Adapté à la régression |
| Risque d’oublier une erreur | Échec signalé automatiquement |
| Difficile pour des centaines de cas | Boucles et données de test |
| Diagnostic graphique | Résultat succès/échec reproductible |
13.1.6 Circuit à tester : DUT ou UUT
Le composant soumis au test est souvent appelé DUT, Device Under Test, ou UUT, Unit Under Test.
| Étiquette d’instance | VHDL |
| DUT : entity work.multiplexeur(rtl) port map ( a => a, b => b, sel => sel, y => y ); |
13.1.7 Limites d’un testbench
- Un testbench incomplet ne prouve pas l’absence d’erreur.
- La simulation RTL ne représente pas automatiquement la métastabilité physique.
- Les délais réels du FPGA nécessitent une analyse temporelle.
- Les entrées inconnues et cas invalides doivent être testés explicitement.
- Le modèle de référence peut lui-même contenir une erreur.
Bonne pratique — Qualité de la vérification Un testbench est un programme de vérification à part entière. Il doit être lisible, documenté, maintenable et soumis à une revue comme le code du circuit. |
13.2 Structure d’un banc de test
Un banc de test contient une entité sans port, une architecture de simulation, les signaux de connexion, l’instance du DUT et un ou plusieurs processus de génération et de vérification.
13.2.1 Entité sans ports
| Entité du testbench | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity tb_multiplexeur is end entity tb_multiplexeur; |
L’entité ne possède ni entrée ni sortie, car le testbench constitue le niveau supérieur de la simulation.
13.2.2 Architecture de test
| Squelette d’architecture | VHDL |
| architecture simulation of tb_multiplexeur is -- Déclarations des signaux et constantes begin -- Instanciation du DUT -- Génération des stimuli -- Vérification des sorties end architecture simulation; |
13.2.3 Signaux de connexion
| Signaux du banc de test | VHDL |
| signal a, b : std_logic := '0'; signal sel : std_logic := '0'; signal y : std_logic; |
Les entrées du DUT sont généralement initialisées afin d’éviter la propagation de valeurs U au début de la simulation.
13.2.4 Instanciation du composant
| Instanciation directe | VHDL |
| DUT : entity work.multiplexeur(rtl) port map ( a => a, b => b, sel => sel, y => y ); |
13.2.5 Génération des stimuli
| Processus de stimuli | VHDL |
| stimuli : process begin a <= '0'; b <= '1'; sel <= '0'; wait for 10 ns; sel <= '1'; wait for 10 ns; wait; end process; |
13.2.6 Observation des sorties
Les sorties peuvent être observées dans le chronogramme, affichées par report ou comparées automatiquement avec assert.
| Observation automatique | VHDL |
| assert y = '1' report "Sortie incorrecte" severity error; |
13.2.7 Architecture complète minimale
| Banc de test minimal — Partie 1 | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use STD.ENV.ALL; entity tb_inverseur is end entity tb_inverseur; architecture simulation of tb_inverseur is signal a : std_logic := '0'; signal y : std_logic; begin DUT : entity work.inverseur(rtl) port map ( a => a, y => y ); |
| Banc de test minimal — Partie 2 | VHDL |
| stimuli : process begin a <= '0'; wait for 1 ns; assert y = '1' severity error; a <= '1'; wait for 1 ns; assert y = '0' severity error; report "Test termine avec succes" severity note; stop; wait; end process; end architecture simulation; |
13.2.8 Plusieurs processus concurrents
Processus | Rôle |
|---|---|
| generation_clk | Produit l’horloge |
| stimuli | Applique reset et entrées |
| moniteur | Observe ou vérifie les sorties |
| timeout | Arrête une simulation bloquée |
13.2.9 Processus de surveillance
| Moniteur simple | VHDL |
| moniteur : process(y) begin report "Nouvelle valeur de y = " & std_logic'image(y) severity note; end process; |
13.2.10 Timeout de sécurité
| Arrêt en cas de simulation trop longue | VHDL |
| timeout : process begin wait for 10 us; assert false report "Timeout : la simulation ne s'est pas terminee" severity failure; end process; |
Sécurité — Simulation bloquée Un timeout protège les tests automatisés contre une attente infinie causée par une condition jamais satisfaite. |
13.3 Génération de l’horloge
Les circuits séquentiels nécessitent une horloge de simulation stable et facile à modifier.
13.3.1 Affectation concurrente demandée
| Horloge de période 20 ns | VHDL |
| signal clk : std_logic := '0'; clk <= not clk after 10 ns; |
Le signal change toutes les 10 ns. Une période complète comprend un temps bas et un temps haut, soit 20 ns, correspondant à 50 MHz.
13.3.2 Utilisation d’une constante
| Période paramétrable dans le testbench | VHDL |
| constant PERIODE_CLK : time := 20 ns; clk <= not clk after PERIODE_CLK / 2; |
Une constante évite de répéter les valeurs temporelles et simplifie la modification de la fréquence.
13.3.3 Processus d’horloge
| Générateur explicite | VHDL |
| generation_clk : process begin clk <= '0'; wait for PERIODE_CLK / 2; clk <= '1'; wait for PERIODE_CLK / 2; end process; |
Le processus recommence automatiquement après end process. Cette forme facilite l’arrêt conditionnel de l’horloge.
13.3.4 Horloge contrôlée
| Arrêt propre du générateur | VHDL |
| generation_clk : process begin while simulation_active loop clk <= '0'; wait for PERIODE_CLK / 2; clk <= '1'; wait for PERIODE_CLK / 2; end loop; wait; end process; |
13.3.5 Rapport cyclique différent de 50 %
| Horloge 30 % / 70 % | VHDL |
| generation_clk : process begin clk <= '1'; wait for 6 ns; clk <= '0'; wait for 14 ns; end process; |
13.3.6 Synchronisation des stimuli sur l’horloge
| Attente d’un front | VHDL |
| wait until rising_edge(clk); d <= nouvelle_donnee; |
Il est souvent préférable de modifier les entrées après un front, afin qu’elles soient stables avant le front suivant.
13.3.7 Vérification après le front
| Prise en compte du cycle delta | VHDL |
| wait until rising_edge(clk); wait for 1 ns; assert q = valeur_attendue severity error; |
Simulation — Pourquoi attendre ? L’affectation d’un signal dans le processus cadencé est planifiée. Une courte attente permet au signal de sortie et aux dépendances combinatoires de se stabiliser avant l’assertion. |
13.3.8 Horloges multiples
Un testbench peut générer plusieurs horloges pour tester des domaines distincts. Les périodes doivent être choisies pour reproduire le système et provoquer différentes relations de phase.
| Deux horloges | VHDL |
| clk_a <= not clk_a after 5 ns; -- 100 MHz clk_b <= not clk_b after 8 ns; -- 62,5 MHz |
13.3.9 Erreurs fréquentes
Erreur | Conséquence | Correction |
|---|---|---|
| clk non initialisé | Propagation de U | Initialiser à '0' |
| Demi-période confondue avec période | Fréquence incorrecte | T = 2 × délai d’inversion |
| Vérification exactement au front | Ancienne valeur lue | Attendre un delta ou 1 ns |
| Horloge jamais arrêtée | Simulation infinie | Utiliser stop ou un drapeau |
| Stimuli trop proches du front | Scénario ambigu | Positionner les données entre deux fronts |
13.4 Génération des stimuli
Les stimuli représentent les entrées appliquées au DUT. Ils doivent couvrir les comportements nominaux, les limites et les situations anormales.
13.4.1 Affectations retardées
| Forme temporelle concurrente | VHDL |
| a <= '0', '1' after 10 ns, '0' after 30 ns, '1' after 50 ns; |
Cette forme planifie plusieurs transactions sur un même signal.
13.4.2 Instruction wait for
| Séquence avec délais relatifs | VHDL |
| a <= '0'; wait for 10 ns; a <= '1'; wait for 20 ns; a <= '0'; wait for 20 ns; |
13.4.3 Instruction wait until
| Attente d’une condition | VHDL |
| wait until rising_edge(clk); wait until ready = '1'; wait until compteur = 9; |
13.4.4 Instruction wait on
| Attente d’un événement | VHDL |
| wait on a, b, sel; |
Le processus reprend lorsqu’un événement se produit sur l’un des signaux listés.
13.4.5 Séquences de test
Phase | Objectif |
|---|---|
| Initialisation | Définir les entrées et activer le reset |
| Démarrage | Relâcher le reset dans des conditions contrôlées |
| Cas nominaux | Tester le fonctionnement attendu |
| Cas limites | Zéro, maximum, changements de direction |
| Cas invalides | Codes non autorisés, valeurs X si pertinent |
| Fin | Rapport final et arrêt automatique |
13.4.6 Procédure locale d’application
| Procédure pour un multiplexeur | VHDL |
| procedure verifier_mux( constant va : in std_logic; constant vb : in std_logic; constant vs : in std_logic; constant attendu : in std_logic ) is begin a <= va; b <= vb; sel <= vs; wait for 1 ns; assert y = attendu report "Erreur multiplexeur" severity error; end procedure; |
Organisation — Portée des signaux Une procédure déclarée dans un processus peut agir sur des signaux visibles dans ce processus. Une procédure de paquetage est généralement écrite avec des paramètres signal explicites. |
13.4.7 Boucles de génération
| Parcours exhaustif de quatre bits | VHDL |
| for valeur in 0 to 15 loop entree <= std_logic_vector(to_unsigned(valeur, 4)); wait for 1 ns; -- Vérification end loop; |
13.4.8 Stimuli pseudo-aléatoires
Pour les blocs complexes, des séquences pseudo-aléatoires peuvent compléter les tests dirigés. Elles ne remplacent pas la couverture systématique des cas essentiels.
- fixer la graine pour reproduire le scénario ;
- conserver un modèle de référence ;
- journaliser les entrées provoquant l’erreur ;
- limiter les valeurs à la plage légale.
13.4.9 Test des valeurs inconnues
| Injection d’une valeur X | VHDL |
| sel <= 'X'; wait for 1 ns; assert y = 'X' report "Le cas invalide n'est pas signale" severity warning; |
Ce test est utile seulement si la spécification prévoit une propagation ou un diagnostic des états inconnus.
13.4.10 Organisation des scénarios
- un commentaire ou une procédure par scénario ;
- des constantes nommées pour les périodes et valeurs limites ;
- des assertions proches du stimulus concerné ;
- un message contenant le numéro ou le nom du scénario ;
- un timeout indépendant ;
- un rapport final unique.
13.5 Assertions
L’instruction assert vérifie une condition booléenne. Le message et la sévérité sont produits lorsque la condition est fausse.
13.5.1 Syntaxe
| Forme générale | VHDL |
| assert condition report "Message de diagnostic" severity niveau; |
13.5.2 Assertion réussie et échouée
| Comparaison de la sortie | VHDL |
| assert somme = somme_attendue report "La somme est incorrecte" severity error; |
Aucun message n’est produit si somme = somme_attendue. Le report est exécuté uniquement lorsque la condition est fausse.
13.5.3 Niveau note
| Message informatif | VHDL |
| assert false report "Debut du scenario 3" severity note; |
Le niveau note signale une information sans indiquer une anomalie fonctionnelle.
13.5.4 Niveau warning
| Situation inhabituelle | VHDL |
| assert code_bcd <= 9 report "Code BCD invalide" severity warning; |
13.5.5 Niveau error
| Erreur fonctionnelle | VHDL |
| assert resultat = attendu report "Resultat incorrect" severity error; |
Le niveau error est adapté à une divergence fonctionnelle que le test doit signaler.
13.5.6 Niveau failure
| Erreur empêchant la poursuite | VHDL |
| assert false report "Timeout de simulation" severity failure; |
Selon les options du simulateur, une sévérité failure peut interrompre immédiatement la simulation.
13.5.7 Comparaison des niveaux
Niveau | Usage conseillé | Effet habituel |
|---|---|---|
| note | Progression, résumé | Information |
| warning | Situation suspecte mais tolérée | Avertissement |
| error | Résultat fonctionnel faux | Échec de test |
| failure | Test impossible ou incohérent | Arrêt fréquent |
13.5.8 Messages détaillés
| Valeur observée et valeur attendue | VHDL |
| assert resultat = attendu report "Erreur : obtenu=" & integer'image(to_integer(resultat)) & ", attendu=" & integer'image(to_integer(attendu)) severity error; |
Bonne pratique — Diagnostic utile Un message doit permettre d’identifier le scénario, les entrées, la sortie obtenue et la sortie attendue sans ouvrir immédiatement le chronogramme. |
13.5.9 Assertion concurrente
| Surveillance permanente | VHDL |
| assert not (lecture = '1' and ecriture = '1') report "Lecture et ecriture actives simultanement" severity error; |
Placée directement dans l’architecture, cette assertion est réévaluée lorsque les signaux de sa condition changent.
13.5.10 Assertion séquentielle
| Dans un processus | VHDL |
| process(clk) begin if rising_edge(clk) then assert not (plein = '1' and push = '1') report "Ecriture dans une FIFO pleine" severity error; end if; end process; |
13.5.11 Utilisation de report
| Instruction report | VHDL |
| report "Tous les cas nominaux sont termines" severity note; |
report produit directement un message. assert false report ... est une ancienne manière équivalente pour générer un message inconditionnel.
13.5.12 Compteur d’erreurs
| Comptabilisation manuelle | VHDL |
| if resultat /= attendu then erreurs := erreurs + 1; report "Erreur numero " & integer'image(erreurs) severity error; end if; |
13.6 Tests automatisés
Un test automatisé calcule la valeur attendue, la compare à la valeur réelle, comptabilise les erreurs et produit un verdict sans intervention visuelle.
13.6.1 Modèle de référence
Le modèle de référence peut être une expression simple, une fonction, une table constante ou un algorithme indépendant du DUT.
| Modèle d’un additionneur | VHDL |
| attendu := to_unsigned(va + vb + vc, attendu'length); |
13.6.2 Comparaison réelle/attendue
| Assertion auto-vérifiante | VHDL |
| assert unsigned(cout & somme) = attendu report "Addition incorrecte" severity error; |
13.6.3 Arrêt automatique
| Arrêt VHDL-2008 | VHDL |
| report "Tous les tests sont termines" severity note; stop; wait; |
La procédure stop est fournie par STD.ENV. L’option --std=08 doit être utilisée avec GHDL.
13.6.4 Arrêt par assertion
| Méthode plus ancienne | VHDL |
| assert false report "Fin de simulation" severity failure; |
Cette méthode peut faire apparaître la fin normale comme un échec. STD.ENV.stop est préférable pour un test réussi.
13.6.5 Couverture des cas limites
Famille de cas | Exemples |
|---|---|
| Minimum | 0, entrées toutes nulles |
| Maximum | 2^N-1, retenue maximale |
| Transition de limite | 9 vers 0, 255 vers 0 |
| Commandes simultanées | reset et enable, load et count |
| Maintien | enable=0 |
| Entrées invalides | X, code BCD 10 à 15 |
13.6.6 Tests exhaustifs
Un test exhaustif parcourt toutes les combinaisons possibles. Il est réaliste pour les petits blocs.
Bloc | Nombre de combinaisons |
|---|---|
| Multiplexeur 2:1 à un bit | 2 × 2 × 2 = 8 |
| Additionneur complet | 2³ = 8 |
| Additionneur 4 bits avec cin | 16 × 16 × 2 = 512 |
| Additionneur 8 bits avec cin | 256 × 256 × 2 = 131 072 |
13.6.7 Test dirigé
Lorsque l’espace d’entrées est trop grand, on sélectionne des cas représentatifs et des limites, puis on complète éventuellement avec des données pseudo-aléatoires.
13.6.8 Organisation par procédures
| Procédure de vérification — Partie 1 | VHDL |
| procedure tester( constant va : natural; constant vb : natural; constant vc : natural ) is variable attendu : unsigned(4 downto 0); begin a <= std_logic_vector(to_unsigned(va, a'length)); b <= std_logic_vector(to_unsigned(vb, b'length)); |
| Procédure de vérification — Partie 2 | VHDL |
| if vc = 0 then cin <= '0'; else cin <= '1'; end if; wait for 1 ns; attendu := to_unsigned(va + vb + vc, 5); assert unsigned(cout & somme) = attendu severity error; end procedure; |
13.6.9 Résumé final
| Verdict | VHDL |
| if erreurs = 0 then report "SUCCES : tous les tests sont valides" severity note; else report "ECHEC : " & integer'image(erreurs) & " erreur(s)" severity error; end if; |
13.6.10 Régression
Une campagne de régression relance automatiquement les bancs de test après chaque modification du code.
- un testbench par composant ;
- une commande ou un script de lancement global ;
- un code de sortie indiquant succès ou échec ;
- des journaux conservés pour diagnostic ;
- des simulations suffisamment courtes.
13.6.11 Analyse des chronogrammes
Même avec des assertions, le chronogramme reste utile pour comprendre un défaut temporel ou une séquence inattendue.
Signal à observer | Question |
|---|---|
| Horloge | Le front actif est-il correct ? |
| Reset | Est-il actif pendant la durée prévue ? |
| Entrées | Sont-elles stables avant le front ? |
| Sorties | Changent-elles au bon instant ? |
| Valeur attendue | Évolue-t-elle comme le modèle ? |
| Signaux internes | Où apparaît la divergence ? |
13.6.12 Bon testbench
- déterministe et reproductible ;
- auto-vérifiant ;
- rapide ;
- lisible ;
- indépendant des détails inutiles du DUT ;
- capable de signaler précisément les erreurs ;
- doté d’un timeout ;
- facile à intégrer dans une régression.
Méthode de création d’un banc de test
1. Lire l’interface et la spécification du DUT.
2. Identifier les cas nominaux et les limites.
3. Créer une entité sans ports.
4. Déclarer les signaux et constantes.
5. Instancier le DUT.
6. Créer l’horloge si nécessaire.
7. Appliquer une initialisation et un reset.
8. Générer les scénarios.
9. Calculer les valeurs attendues.
10. Ajouter les assertions.
11. Ajouter un timeout et un arrêt propre.
12. Analyser les messages et le chronogramme.
Checklist avant lancement
- Tous les signaux d’entrée sont-ils initialisés ?
- La période d’horloge est-elle correcte ?
- Le reset est-il appliqué conformément au DUT ?
- Les assertions sont-elles placées après stabilisation ?
- Le modèle attendu utilise-t-il la bonne largeur ?
- Les limites sont-elles testées ?
- La simulation se termine-t-elle automatiquement ?
- Un timeout est-il présent ?
Erreurs fréquentes
Erreur | Conséquence | Correction |
|---|---|---|
| Entité de test avec des ports inutiles | Environnement moins autonome | Utiliser une entité vide |
| Entrées non initialisées | Valeurs U et X | Ajouter des valeurs initiales |
| Assertion au même instant que l’affectation | Ancienne sortie lue | Attendre la stabilisation |
| Mauvaise largeur du modèle | Comparaison fausse | Dimensionner explicitement |
| Pas de stop | Simulation infinie | STD.ENV.stop |
| Test seulement visuel | Régression difficile | Ajouter assert |
| Absence de timeout | Blocage silencieux | Processus de sécurité |
Travaux pratiques
TP 10 — Création de bancs de test
1. Objectifs du TP
- tester un multiplexeur 4 vers 1 ;
- réaliser le test exhaustif d’un additionneur 4 bits ;
- tester un compteur modulo 10 ;
- utiliser des assertions détaillées ;
- arrêter automatiquement les simulations ;
- analyser les chronogrammes avec GTKWave.
2. Organisation du projet
| Arborescence proposée | Arborescence |
| tp10_bancs_test/ ├── src/ │ ├── mux4.vhd │ ├── additionneur4.vhd │ └── compteur_modulo10.vhd └── sim/ ├── tb_mux4.vhd ├── tb_additionneur4.vhd └── tb_compteur_modulo10.vhd |
3. Circuit 1 — Multiplexeur 4 vers 1
| mux4.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity mux4 is port ( a, b, c, d : in std_logic_vector(7 downto 0); sel : in std_logic_vector(1 downto 0); y : out std_logic_vector(7 downto 0) ); end entity mux4; architecture rtl of mux4 is begin with sel select y <= a when "00", b when "01", c when "10", d when others; end architecture rtl; |
4. Banc de test du multiplexeur
| tb_mux4.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use STD.ENV.ALL; entity tb_mux4 is end entity tb_mux4; architecture simulation of tb_mux4 is signal a : std_logic_vector(7 downto 0) := x"11"; signal b : std_logic_vector(7 downto 0) := x"22"; signal c : std_logic_vector(7 downto 0) := x"44"; signal d : std_logic_vector(7 downto 0) := x"88"; signal sel : std_logic_vector(1 downto 0) := "00"; signal y : std_logic_vector(7 downto 0); begin DUT : entity work.mux4(rtl) port map ( a => a, b => b, c => c, d => d, sel => sel, y => y ); |
| tb_mux4.vhd — Test automatisé | VHDL |
| stimuli : process type table_t is array (0 to 3) of std_logic_vector(7 downto 0); constant attendu : table_t := ( x"11", x"22", x"44", x"88" ); begin for i in 0 to 3 loop sel <= std_logic_vector(to_unsigned(i, 2)); wait for 1 ns; assert y = attendu(i) report "MUX4 : erreur pour sel=" & integer'image(i) severity error; end loop; report "MUX4 : 4 selections valides" severity note; stop; wait; end process; end architecture simulation; |
5. Test exhaustif du multiplexeur un bit
| Boucles imbriquées | VHDL |
| for va in 0 to 1 loop for vb in 0 to 1 loop for vs in 0 to 1 loop -- Appliquer va, vb et vs -- Comparer y à l'entrée sélectionnée end loop; end loop; end loop; |
6. Circuit 2 — Additionneur 4 bits
| additionneur4.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity additionneur4 is port ( a, b : in std_logic_vector(3 downto 0); cin : in std_logic; somme : out std_logic_vector(3 downto 0); cout : out std_logic ); end entity additionneur4; architecture rtl of additionneur4 is signal resultat : unsigned(4 downto 0); begin resultat <= resize(unsigned(a), 5) + resize(unsigned(b), 5) + to_unsigned( 1 when cin = '1' else 0, 5 ); somme <= std_logic_vector(resultat(3 downto 0)); cout <= resultat(4); end architecture rtl; |
7. Banc de test exhaustif de l’additionneur
| tb_additionneur4.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use STD.ENV.ALL; entity tb_additionneur4 is end entity tb_additionneur4; architecture simulation of tb_additionneur4 is signal a, b : std_logic_vector(3 downto 0) := (others => '0'); signal cin : std_logic := '0'; signal somme : std_logic_vector(3 downto 0); signal cout : std_logic; begin DUT : entity work.additionneur4(rtl) port map ( a => a, b => b, cin => cin, somme => somme, cout => cout ); |
| tb_additionneur4.vhd — Boucles exhaustives, partie 1 | VHDL |
| stimuli : process variable attendu : unsigned(4 downto 0); variable erreurs : natural := 0; begin for va in 0 to 15 loop for vb in 0 to 15 loop for vc in 0 to 1 loop a <= std_logic_vector( to_unsigned(va, a'length) ); b <= std_logic_vector( to_unsigned(vb, b'length) ); if vc = 0 then cin <= '0'; else cin <= '1'; end if; wait for 1 ns; attendu := to_unsigned(va + vb + vc, 5); |
| tb_additionneur4.vhd — Boucles exhaustives, partie 2 | VHDL |
| if unsigned(cout & somme) /= attendu then erreurs := erreurs + 1; report "Erreur : a=" & integer'image(va) & ", b=" & integer'image(vb) & ", cin=" & integer'image(vc) severity error; end if; end loop; end loop; end loop; assert erreurs = 0 report integer'image(erreurs) & " erreur(s) detectee(s)" severity error; report "Additionneur : 512 cas testes" severity note; stop; wait; end process; end architecture simulation; |
8. Cas limites de l’additionneur
a | b | cin | Résultat attendu |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 15 | 0 | 0 | 15 |
| 15 | 1 | 0 | 16 |
| 15 | 15 | 0 | 30 |
| 15 | 15 | 1 | 31 |
9. Circuit 3 — Compteur modulo 10
| compteur_modulo10.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_modulo10 is port ( clk : in std_logic; reset : in std_logic; enable : in std_logic; q : out unsigned(3 downto 0) ); end entity compteur_modulo10; architecture rtl of compteur_modulo10 is signal compteur : unsigned(3 downto 0); begin process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then if enable = '1' then if compteur = 9 then compteur <= (others => '0'); else compteur <= compteur + 1; end if; end if; end if; end process; q <= compteur; end architecture rtl; |
10. Banc de test du compteur
| tb_compteur_modulo10.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use STD.ENV.ALL; entity tb_compteur_modulo10 is end entity tb_compteur_modulo10; architecture simulation of tb_compteur_modulo10 is constant PERIODE : time := 20 ns; signal clk : std_logic := '0'; signal reset : std_logic := '0'; signal enable : std_logic := '0'; signal q : unsigned(3 downto 0); begin clk <= not clk after PERIODE / 2; DUT : entity work.compteur_modulo10(rtl) port map ( clk => clk, reset => reset, enable => enable, q => q ); |
| tb_compteur_modulo10.vhd — Reset et comptage | VHDL |
| stimuli : process variable attendu : natural; begin reset <= '1'; enable <= '0'; wait for 2 * PERIODE; assert q = 0 report "Reset incorrect" severity error; reset <= '0'; enable <= '1'; for cycle in 1 to 25 loop wait until rising_edge(clk); wait for 1 ns; attendu := cycle mod 10; assert to_integer(q) = attendu report "Compteur incorrect au cycle " & integer'image(cycle) severity error; end loop; |
| tb_compteur_modulo10.vhd — Maintien et fin | VHDL |
| enable <= '0'; wait until rising_edge(clk); wait for 1 ns; assert to_integer(q) = 5 report "Le compteur ne conserve pas sa valeur" severity error; report "Compteur modulo 10 valide" severity note; stop; wait; end process; timeout : process begin wait for 2 us; assert false report "Timeout compteur" severity failure; end process; end architecture simulation; |
11. Analyse des chronogrammes
- Ajouter clk, reset, enable et q au chronogramme du compteur.
- Vérifier l’action immédiate du reset asynchrone.
- Observer que q change après le front montant.
- Vérifier le passage 9 vers 0.
- Vérifier le maintien lorsque enable vaut 0.
- Comparer le chronogramme aux assertions.
12. Commandes GHDL
| Compilation des circuits | Terminal |
| ghdl -a --std=08 src/mux4.vhd ghdl -a --std=08 src/additionneur4.vhd ghdl -a --std=08 src/compteur_modulo10.vhd |
| Simulation du multiplexeur | Terminal |
| ghdl -a --std=08 sim/tb_mux4.vhd ghdl -e --std=08 tb_mux4 ghdl -r --std=08 tb_mux4 |
| Simulation avec chronogramme | Terminal |
| ghdl -a --std=08 sim/tb_compteur_modulo10.vhd ghdl -e --std=08 tb_compteur_modulo10 ghdl -r --std=08 tb_compteur_modulo10 --vcd=tb_compteur_modulo10.vcd gtkwave tb_compteur_modulo10.vcd |
13. Résultats à consigner
Test | Nombre de cas | Erreurs | Verdict |
|---|---|---|---|
| Multiplexeur 4:1 | 4 sélections | ||
| Additionneur 4 bits | 512 combinaisons | ||
| Compteur modulo 10 | Reset + 25 cycles + maintien | ||
| Timeout | 1 condition de sécurité |
14. Questions d’analyse
1. Pourquoi l’entité d’un testbench ne possède-t-elle pas de ports ?
2. Quelle différence existe entre wait for et wait until ?
3. Pourquoi l’horloge clk <= not clk after 10 ns a-t-elle une période de 20 ns ?
4. Pourquoi faut-il attendre après un front avant de vérifier q ?
5. Quand utiliser severity warning plutôt que error ?
6. Pourquoi un test exhaustif est-il réaliste pour un additionneur 4 bits ?
7. Quel est le rôle du modèle attendu ?
8. Pourquoi un timeout est-il utile ?
9. Pourquoi arrêter la simulation avec STD.ENV.stop ?
10. Quel intérêt conserve le chronogramme avec un testbench auto-vérifiant ?
15. Extensions proposées
- Créer une procédure réutilisable pour le multiplexeur.
- Tester une valeur X sur le sélecteur.
- Ajouter une sortie retenue au compteur.
- Créer un testbench pour une machine à états.
- Lire les vecteurs de test depuis un fichier.
- Écrire les résultats dans un fichier texte.
- Créer un script lançant tous les tests.
- Ajouter des tests pseudo-aléatoires reproductibles.
16. Barème indicatif
Critère | Points |
|---|---|
| Structure correcte des trois testbenches | 3 |
| Test du multiplexeur | 2 |
| Test exhaustif de l’additionneur | 4 |
| Test du compteur et de son reset | 3 |
| Assertions et messages | 3 |
| Arrêt automatique et timeout | 2 |
| Analyse des chronogrammes | 2 |
| Qualité du code et du compte rendu | 1 |
| Total | 20 |
Synthèse du chapitre
Notion | Résumé |
|---|---|
| Testbench | Environnement de simulation du DUT. |
| Entité vide | Niveau supérieur sans interface externe. |
| Stimuli | Séquences appliquées aux entrées. |
| Horloge | Signal périodique généré dans le testbench. |
| wait for | Attente d’une durée. |
| wait until | Attente d’une condition ou d’un front. |
| assert | Vérification d’une condition. |
| severity | Niveau note, warning, error ou failure. |
| Modèle attendu | Référence utilisée pour la comparaison. |
| Test exhaustif | Parcours de toutes les combinaisons. |
| stop | Arrêt propre VHDL-2008. |
| Timeout | Protection contre une simulation bloquée. |
Autoévaluation
1. Un testbench est-il synthétisé ?
Réponse : Non, il sert à la simulation.
2. Pourquoi son entité est-elle vide ?
Réponse : Parce qu’il est le niveau supérieur de l’environnement de test.
3. Que fait wait for 10 ns ?
Réponse : Il suspend le processus pendant 10 ns.
4. Que fait wait until rising_edge(clk) ?
Réponse : Il attend le prochain front montant.
5. Quand une assertion affiche-t-elle son message ?
Réponse : Lorsque sa condition est fausse.
6. Quel niveau signale une erreur fonctionnelle ?
Réponse : severity error.
7. Comment arrêter proprement en VHDL-2008 ?
Réponse : Avec STD.ENV.stop.
8. Pourquoi calculer une sortie attendue ?
Réponse : Pour automatiser la comparaison.
9. Pourquoi ajouter un timeout ?
Réponse : Pour arrêter un test bloqué.
10. Pourquoi conserver un chronogramme ?
Réponse : Pour diagnostiquer la chronologie d’une erreur.
Exercice de consolidation
Écrire un testbench auto-vérifiant pour un comparateur unsigned de 3 bits produisant egal, inferieur et superieur. Parcourir les 64 couples d’entrées.
| Correction proposée — Partie 1 | VHDL |
| stimuli : process begin for va in 0 to 7 loop for vb in 0 to 7 loop a <= to_unsigned(va, a'length); b <= to_unsigned(vb, b'length); wait for 1 ns; if va = vb then assert egal = '1' and inferieur = '0' and superieur = '0' severity error; |
| Correction proposée — Partie 2 | VHDL |
| elsif va < vb then assert egal = '0' and inferieur = '1' and superieur = '0' severity error; else assert egal = '0' and inferieur = '0' and superieur = '1' severity error; end if; end loop; end loop; report "Comparateur : 64 couples valides" severity note; stop; wait; end process; |
Suite du cours — Transition vers la suite Après les bancs de test fonctionnels, le cours peut approfondir les événements, délais, cycles delta, attributs de signaux et différences entre simulation fonctionnelle et simulation temporelle. |