Leçon 13 sur 22

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.1Rôle d’un testbenchComprendre la stratégie de validation
13.2Structure généraleAssembler l’environnement de test
13.3Génération de l’horlogeCréer une base de temps fiable
13.4Génération des stimuliReproduire des scénarios
13.5AssertionsDétecter et signaler les erreurs
13.6Tests automatisésConstruire une vérification auto-contrôlée
TP 10Multiplexeur, additionneur et compteurValider 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 logiqueXOR utilisé à la place de OR
Erreur de prioritéload traité après enable
Erreur de tailleRetenue supprimée
Erreur temporelle RTLSortie vérifiée avant le cycle delta
Erreur de resetSortie non réinitialisée
Erreur de séquenceCompteur 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 VHDLLe code est-il syntaxiquement et sémantiquement valide ?
Simulation RTLLe comportement fonctionnel est-il correct ?
SynthèseQuel matériel est inféré ?
Simulation post-synthèse éventuelleLe réseau synthétisé reste-t-il cohérent ?
Analyse temporelleLes contraintes sont-elles respectées ?
Validation FPGALe 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 chronogrammeAssertions et modèle de référence
Adapté à l’explorationAdapté à la régression
Risque d’oublier une erreurÉchec signalé automatiquement
Difficile pour des centaines de casBoucles et données de test
Diagnostic graphiqueRé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_clkProduit l’horloge
stimuliApplique reset et entrées
moniteurObserve ou vérifie les sorties
timeoutArrê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 UInitialiser à '0'
Demi-période confondue avec périodeFréquence incorrecteT = 2 × délai d’inversion
Vérification exactement au frontAncienne valeur lueAttendre un delta ou 1 ns
Horloge jamais arrêtéeSimulation infinieUtiliser stop ou un drapeau
Stimuli trop proches du frontScénario ambiguPositionner 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

InitialisationDéfinir les entrées et activer le reset
DémarrageRelâcher le reset dans des conditions contrôlées
Cas nominauxTester le fonctionnement attendu
Cas limitesZéro, maximum, changements de direction
Cas invalidesCodes non autorisés, valeurs X si pertinent
FinRapport 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

noteProgression, résuméInformation
warningSituation suspecte mais toléréeAvertissement
errorRésultat fonctionnel fauxÉchec de test
failureTest impossible ou incohérentArrê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

Minimum0, entrées toutes nulles
Maximum2^N-1, retenue maximale
Transition de limite9 vers 0, 255 vers 0
Commandes simultanéesreset et enable, load et count
Maintienenable=0
Entrées invalidesX, 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 bit2 × 2 × 2 = 8
Additionneur complet2³ = 8
Additionneur 4 bits avec cin16 × 16 × 2 = 512
Additionneur 8 bits avec cin256 × 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

HorlogeLe front actif est-il correct ?
ResetEst-il actif pendant la durée prévue ?
EntréesSont-elles stables avant le front ?
SortiesChangent-elles au bon instant ?
Valeur attendueÉvolue-t-elle comme le modèle ?
Signaux internesOù 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 inutilesEnvironnement moins autonomeUtiliser une entité vide
Entrées non initialiséesValeurs U et XAjouter des valeurs initiales
Assertion au même instant que l’affectationAncienne sortie lueAttendre la stabilisation
Mauvaise largeur du modèleComparaison fausseDimensionner explicitement
Pas de stopSimulation infinieSTD.ENV.stop
Test seulement visuelRégression difficileAjouter assert
Absence de timeoutBlocage silencieuxProcessus 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

0000
150015
151016
1515030
1515131

 

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:14 sélections  
Additionneur 4 bits512 combinaisons  
Compteur modulo 10Reset + 25 cycles + maintien  
Timeout1 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 testbenches3
Test du multiplexeur2
Test exhaustif de l’additionneur4
Test du compteur et de son reset3
Assertions et messages3
Arrêt automatique et timeout2
Analyse des chronogrammes2
Qualité du code et du compte rendu1
Total20

 

Synthèse du chapitre

Notion

Résumé

TestbenchEnvironnement de simulation du DUT.
Entité videNiveau supérieur sans interface externe.
StimuliSéquences appliquées aux entrées.
HorlogeSignal périodique généré dans le testbench.
wait forAttente d’une durée.
wait untilAttente d’une condition ou d’un front.
assertVérification d’une condition.
severityNiveau note, warning, error ou failure.
Modèle attenduRéférence utilisée pour la comparaison.
Test exhaustifParcours de toutes les combinaisons.
stopArrêt propre VHDL-2008.
TimeoutProtection 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.