VHDL pour la simulation

Un simulateur est un logiciel capable d’exécuter une description VHDL pour reproduire sur ordinateur le comportement du circuit en développement. Un simulateur permet de visualiser sous forme de chronogrammes les signaux internes de la description, qui sont souvent inaccessibles dans le circuit final. Cela permet de détecter et de corriger des dysfonctionnements avant de lancer la synthèse, voire la fabrication, du circuit.

Notion de banc de test

Pour tester le fonctionnement d’une entité, une pratique fréquente consiste à écrire une autre entité banc de test (ou en anglais test bench) qui modélisera l’environnement. Généralement, un banc de test n’a pas de ports d’entrée/sortie.

Dans l’architecture de test, on trouvera typiquement :

Comme le banc de test ne représentent pas un circuit que l’on cherche à réaliser, son architecture pourra utiliser des types de données et des instructions non synthétisables, dont le rôle est de décrire des scénarios de fonctionnement et de fournir des indications utiles au développeur.

Voici un exemple de banc de test pour l’entité CounterModN :

entity CounterTestbench is
    -- Pas de port.
end CounterTestbench;

library ieee;
use ieee.std_logic_1164.all;

architecture Simulation of CounterTestbench is
    -- Déclaration des signaux de test.
    signal clk   : std_logic := '0';
    signal reset : std_logic := '1';
    signal inc   : std_logic := '0';
    signal value : integer range 0 to 9;
    signal cycle : std_logic;
begin
    -- Une instance de l'entité à tester.
    counter_inst : entity work.CounterModN(Behavioral)
        generic map(
            N => 10
        )
        port map(
            clk_i   => clk,
            reset_i => reset,
            inc_i   => inc,
            value_o => value,
            cycle_o => cycle
        );

    -- Génération d'une horloge de période 10 ns.
    clk <= not clk after 5 ns;

    -- Désactiver le signal reset au bout de 10 ns.
    reset <= '0' after 10 ns;

    -- Commencer à incrémenter le compteur à t=30 ns,
    -- terminer  à t=230 ns.
    inc <= '1' after 30 ns, '0' after 230 ns;

    -- Vérifier les valeurs de value et cycle.
    process
        variable value_prev : integer range 0 to 9;
    begin
        -- Vérifier la valeur du compteur après la désactivation du reset.
        wait until falling_edge(reset);
        assert value = 0
            report "value devrait être à 0"
            severity ERROR;

        loop
            -- Mémoriser la valeur courante du compteur et attendre
            -- un front montant de l'horloge.
            value_prev := value;
            wait until rising_edge(clk);

            -- value doit se mettre à jour de façon synchrone.
            -- Attendre un peu après le front pour que les valeurs
            -- des signaux soient stabilisées.
            wait for 1 ns;

            -- Vérifier la nouvelle valeur du compteur.
            if inc = '0' then
                assert value = value_prev
                    report "value devrait être stable"
                    severity ERROR;
            elsif value_prev = 9 then
                assert value = 0
                    report "value aurait dû revenir à 0"
                    severity ERROR;
            else
                assert value = value_prev + 1
                    report "value aurait dû s'incrémenter"
                    severity ERROR;
            end if;

            -- Vérifier la valeur de la sortie cycle.
            assert (value = 9 and cycle = '1') or (value /= 9 and cycle = '0')
                report "cycle n'a pas l'état souhaité"
                severity ERROR;
        end loop;
    end process;
end Simulation;

Gestion du temps avec le type time et la fonction now

VHDL permet la déclaration de types physiques, c’est-à-dire des types de données capables de représenter des grandeurs physique sous la forme d’un nombre et d’une unité de mesure. En pratique, on utilise le plus souvent le type prédéfini time dont voici la déclaration :

type time is range -9223372036854775808 fs to 9223372036854775807 fs
    units
        fs;
        ps  = 1000 fs;
        ns  = 1000 ps;
        us  = 1000 ns;
        ms  = 1000 us;
        sec = 1000 ms;
        min =   60 sec;
        hr  =   60 min;
    end units;

Ce type permet de représenter des valeurs de temps comprises entre -9223 et 9223 secondes environ, avec une précision d’une femtoseconde (10-15 s). Voici quelques exemples de déclarations de constantes utilisant ce type :

constant CLK_PERIOD  : time := 10 ns;
constant LATENCY     : time := CLK_PERIOD * 16;
constant RETRY_DELAY : time := 3 sec;
constant TOTAL_DELAY : time := RETRY_DELAY + 200 ms;

Comme le montrent les déclarations des constantes LATENCY et TOTAL_DELAY, on peut appliquer des opérations arithmétiques courante sur les types physiques à condition de respecter certaines règles : on ne peut les multiplier ou les diviser que par des nombres sans unité ; on ne peut les additionner ou les soustraire que si elles sont de même type.

VHDL définit également la fonction now, qui retourne le temps écoulé depuis le début de la simulation. On peut l’utiliser dans un processus pour calculer des durées entre des événements.

Le type time et la fonction now sont déclarées dans le paquetage standard de VHDL. Vous n’avez pas besoin de les déclarer vous-mêmes ni d’importer ce paquetage pour les utiliser.

Dans une valeur littérale avec unité, il faut toujours séparer le nombre et son unité par un espace :

constant CLK_PERIOD  : time := 10 ns; -- Correct
constant CLK_PERIOD  : time := 10ns;  -- Incorrect

Génération de séquences de valeurs dans les affectations concurrentes

Voici trois exemples utilisés dans le banc de test du compteur :

architecture Simulation of CounterTestbench is
    signal clk   : std_logic := '0';
    signal reset : std_logic := '1';
    signal inc   : std_logic := '0';
    ...
begin
    ...
    clk   <= not clk after 5 ns;
    reset <= '0' after 10 ns;
    inc   <= '1' after 30 ns, '0' after 230 ns;
    ...
end Simulation;

À la date t=0, les signaux clk, reset et inc ont la valeur initiale indiquée dans leurs déclarations.

Dans les instructions d’affectation concurrentes, La clause after permet d’affecter une valeur à un signal au bout d’un certain temps après l’exécution de l’instruction :

Attente d’évenements avec l’instruction wait

L’instruction wait peut être utilisée dans un processus pour mettre en pause l’exécution pendant une certains durée ou jusqu’à la détection d’un événement. Elle se présente typiquement sous trois formes :

wait for durée;
wait on nom de signal, nom de signal, ...;
wait until condition;
wait;

L’instruction wait until attend un événement qui rend la condition vraie. Si la condition est déjà vraie au moment d’exécuter cette instruction, le processus sera mis en pause jusqu’à ce qu’elle devienne fausse puis vraie à nouveau.

Boucles

Nous connaissons déjà la boucle for, qui est synthétisable lorsque le compteur de boucle parcourt un intervalle connu. Pour décrire des scénarios de tests répétitifs, on peut également utiliser les boucles infinies et les boucles tant que avec les syntaxes suivantes :

-- Exécuter le corps de boucle indéfiniment.
loop
    instruction séquentielle
    ...
    instruction séquentielle
end loop;

-- Exécuter le corps de boucle tant que la condition est vraie.
while condition loop
    instruction séquentielle
    ...
    instruction séquentielle
end loop;

À l’intérieur des boucles, il est possible d’utiliser les instruction next et exit.

Comme la boucle for, les instructions loop, while, next et exit ne sont autorisées que dans les processus.

Il est inutile d’écrire un processus qui ne contiendrait qu’une boucle infinie. En effet, le corps d’un processus est exécuté à chaque fois qu’il détecte un événement auquel il est sensible, ce qui s’apparente déjà à une boucle infinie. Les trois écritures suivantes produisent donc le même comportement :

process(clk_i)
begin
    if rising_edge(clk_i) then
        ...
    end if;
end process;

process
begin
    wait until rising_edge(clk_i);
    ...
end process;

process
begin
    loop
        wait until rising_edge(clk_i);
        ...
    end loop;
end process;

Affichage et gestion des erreurs avec les instructions report et assert

L’instruction report permet d’afficher un message dans la console du simulateur. Elle peut se présenter sous deux formes :

report chaîne de caractères;
report chaîne de caractères severity niveau;

Le mot-clé severity permet d’indiquer le niveau de gravité du message. Ce champ peut prendre les valeurs symboliques suivantes :

Niveau Signification
NOTE Information
WARNING Avertissement
ERROR Erreur
FAILURE Échec

L’interprétation de ces niveaux dépend du logiciel de simulation et de la façon dont il est configuré. Par exemple, en présence d’un message de type FAILURE on peut souhaiter que la simulation s’arrête immédiatement, alors qu’elle peut continuer dans les autres cas.

L’instruction assert vérifie si une condition est vraie. Lorsque la condition est fausse, elle affiche un message à la manière de l’instruction report. Les quatre formes de l’instruction assert sont :

assert condition;
assert condition severity niveau;
assert condition report chaîne de caractères;
assert condition report chaîne de caractères severity niveau;

Dans l’instruction report, en l’absence d’une clause severity, c’est le niveau NOTE qui est utilisé.

Dans l’instruction assert, le niveau par défaut est ERROR. En l’absence d’une clause report, le message affiché sera "Assertion violation".

Manipulation de chaînes de caractères

En VHDL, les chaînes de caractères sont de type string et s’écrivent entre guillemets :

report "Exemple de message";

Pour construire des chaînes de caractères qui contiennent des données de simulation, on peut s’appuyer sur deux opérations :

chaîne de caractères & chaîne de caractères & ...
nom de type'image(expression)

Par exemple, si value et value_prev sont de type integer, on peut écrire :

assert value = value_prev
    report "value vaut "           & integer'image(value)
         & " mais devrait valoir " & integer'image(value_prev)
    severity ERROR;

Manipulation de fichiers

À compléter.