Architectures

Définition

Après avoir décrit l’interface d’un circuit sous la forme d’une entité, il faut décrire le fonctionnement ou la structure interne de ce circuit. En VHDL, cette description s’appelle une architecture.

Il est possible d’associer plusieurs architectures à une même entité. Cela permet de proposer plusieurs descriptions interchangeables d’un même circuit, chacune répondant à des préoccupations différentes, par exemple :

Cette page illustre la notion d’architecture à travers des exemples. Le détail de la syntaxe est expliqué dans la section : L’essentiel de VHDL / Unités de conception / Architecture.

Exemples

Les exemples qui suivent servent à illustrer la notion d’architecture en prenant toujours pour point de départ une définition du circuit que nous voulons décrire. Cette définition peut se présenter sous la forme d’une table de vérité, d’équations, d’algorithmes ou de diagrammes.

Ce n’est pas grave si vous ne comprenez pas tout. Cette page a pour but de vous donner un aperçu de ce qu’est une architecture et de ce qu’elle représente. Nous présenterons plus en détail les instructions du langage VHDL dans la suite de ce document.

Décodeur 7 segments

Cette architecture est associée à l’entité SegmentDecoder présentée dans la page Entités.

Pour écrire cette architecture, nous nous sommes inspirés du tableau suivant, qui indique comment afficher les chiffres de 0 à 9 et les lettres de A à F en fonction de la valeur d’entrée.

Numérotation des segments sur un afficheur 7 segments

valeur segments
0123456
0 1111110
1 0110000
2 1101101
3 1111001
4 0110011
5 1011011
6 1011111
7 1110000
8 1111111
9 1111011
10 1110111
11 0011111
12 1001110
13 0111101
14 1001111
15 1000111

Voici le code source VHDL correspondant. L’entité a été recopiée pour faciliter la compréhension.

entity SegmentDecoder is
    port(
        digit_i    : in  integer range 0 to 15;
        segments_o : out std_logic_vector(0 to 6)
    );
end SegmentDecoder;

architecture TruthTable of SegmentDecoder is
begin
    with digit_i select
        segments_o <= "1111110" when 0,
                      "0110000" when 1,
                      "1101101" when 2,
                      "1111001" when 3,
                      "0110011" when 4,
                      "1011011" when 5,
                      "1011111" when 6,
                      "1110000" when 7,
                      "1111111" when 8,
                      "1111011" when 9,
                      "1110111" when 10,
                      "0011111" when 11,
                      "1001110" when 12,
                      "0111101" when 13,
                      "1001111" when 14,
                      "1000111" when 15;
end TruthTable;

L’architecture contient une unique instruction d’affectation de signal avec sélection qui met en correspondance les 16 cas possibles du port digit_i avec des valeurs binaires destinés aux segments de l’afficheur. Le résultat est affecté au port de sortie segments_o.

Compteur synchrone générique

L’architecture ci-dessous est associée à l’entité CounterModN présentée dans la page Entités. Voici un schéma du circuit compteur que nous souhaitons décrire. N est une valeur entière paramétrable qui détermine le modulo du compteur.

Architecture pour CounterModN

Afficher la légende des schémas.

Nous avons décrit ce circuit en VHDL de la manière suivante :

entity CounterModN is
    generic(
        N : positive
    );
    port(
        clk_i, reset_i, inc_i : in  std_logic;
        value_o               : out integer range 0 to N - 1;
        cycle_o               : out std_logic
    );
end CounterModN;

architecture Behavioral of CounterModN is
    signal value_reg, value_next : integer range 0 to N - 1;
begin
    -- value_reg est mémorisé dans un registre avec :
    --  * remise à zéro asynchrone,
    --  * mise à jour sur front montant de clk_i,
    --  * validation par le signal inc_i.
    p_value_reg : process(clk_i, reset_i)
    begin
        if reset_i = '1' then
            value_reg <= 0;
        elsif rising_edge(clk_i) then
            if inc_i = '1' then
                value_reg <= value_next;
            end if;
        end if;
    end process p_value_reg;

    -- Calcul de la valeur suivante du registre
    value_next <= 0 when value_reg = N - 1 else value_reg + 1;

    value_o <= value_reg;
    cycle_o <= '1' when inc_i = '1' and value_reg = N - 1 else '0';
end Behavioral;

Le registre met à jour le signal value_reg sur les fronts montants de clk_i lorsque l’entrée inc_i vaut '1'. Il est décrit par le processus p_value_reg. L’expression rising_edge(clk_i) permet de détecter un front montant de clk_i.

L’état suivant du registre est calculé par une instruction d’affectation concurrente qui affecte son résulat au signal value_next. Si le compteur a déjà atteint sa valeur maximale (N - 1), alors sa prochaine valeur sera 0, sinon on ajoute 1 à la valeur courante.

La sortie cycle_o vaut '1' lorsqu’on demande au compteur de s’incrémenter alors qu’il a déjà atteint sa valeur maximale. Cela signifie que le compteur repassera à 0 au prochain front d’horloge. Cette sortie pourra être utilisée pour mettre des compteurs en cascade.

Par convention, nous donnerons le suffixe _reg aux signaux qui représentent des sorties de bascules ou de registres (mis à jour sur des fronts d’horloge).

Le suffixe _next sera utilisé pour un signal qui transporte la valeur qu’un registre doit prendre au front d’horloge suivant ; un tel signal représente l’entrée D d’une bascule ou d’un registre.

Calcul de PGCD

La recherche du plus grand commun diviseur (PGCD) – ou en anglais Greatest Common Divisor (GCD) – entre deux nombres entiers peut s’effectuer par soustractions successives. Ce n’est pas la méthode la plus efficace, mais elle se prête bien à la réalisation d’un circuit. En C, nous proposons la fonction suivante (pour a et b strictement positifs) :

int gcd(int a, int b) {
    while (a != b) {
        if (a > b) {
            a = a - b;
        }
        else {
            b = b - a;
        }
    }
    return a;
}

Voici une démonstration de son fonctionnement :

a b

En C, une fonction ne s’exécute que lorsqu’elle est appelée. La fonction gcd ci-dessus a un début et une fin bien identifiés. En VHDL, une architecture représente un circuit qui fonctionne en permanence. Il n’y a pas de début ni de fin. C’est à nous de mettre en place des mécanismes de synchronisation pour déclencher un nouveau calcul, séquencer les opérations au cours du temps, et indiquer la disponibilité du résultat. L’entité GCD possédera les ports suivants :

Port Direction Type Rôle
clk_i entrée Logique Commande de mise à jour des éléments de mémorisation
load_i entrée Logique Commande de chargement d’un nouveau couple d’entiers
a_i, b_i entrées Entiers (> 0) Les valeurs dont on souhaite calculer le PGCD
g_o sortie Entier (> 0) Le résultat
done_o sortie Logique Indicateur de fin du calcul
entity GCD is
    port(
        clk_i, load_i : in  std_logic;
        a_i, b_i      : in  positive;
        g_o           : out positive;
        done_o        : out std_logic
    );
end GCD;

Dans notre fonction C, nous agissons directement sur ses paramètres a et b qui changent de valeur à chaque itération de la boucle. En VHDL, il n’est pas possible de modifier les valeurs des ports d’entrée. Les valeurs intermédiaires de a et b seront affectées à deux signaux internes a_reg et b_reg. Elles seront mémorisées dans des registres mis à jour à chaque front montant de clk_i.

Un schéma du circuit correspondant est proposé ci-dessous.

Architecture pour le calcul de PGCD

Voici une démonstration de son fonctionnement, avec une horloge de période 2 secondes.

load_i clk_i
a_i a_next a_reg 0 g_o
b_i b_next b_reg 0 done_o

Nous avons écrit deux architectures qui réalisent ce fonctionnement. L’architecture Detailed, ci-dessous, sépare explicitement les fonctions combinatoires et les fonctions logiques séquentielles :

architecture Detailed of GCD is
    signal a_next, b_next : positive;
    signal a_reg,  b_reg  : positive;
begin
    -- Registres a_reg et b_reg
    -- mis à jour sur les fronts montants de clk_i
    p_ab_reg : process(clk_i)
    begin
        if rising_edge(clk_i) then
            a_reg <= a_next;
            b_reg <= b_next;
        end if;
    end process p_ab_reg;

    -- Calcul de la valeur suivante de a_reg
    a_next <= a_i           when load_i = '1'  else
              a_reg - b_reg when a_reg > b_reg else
              a_reg;

    -- Calcul de la valeur suivante de b_reg
    b_next <= b_i           when load_i = '1'  else
              b_reg - a_reg when b_reg > a_reg else
              b_reg;

    -- Affectation des sorties
    g_o    <= a_reg;
    done_o <= '1' when a_reg = b_reg else '0';
end Detailed;

Comme on le voit, cette description VHDL ne contient pas de boucle. Elle décrit le fonctionnement instantané du circuit : les instructions concurrentes réagissent aux changements des ports d’entrée et des signaux internes, et calculent de nouvelles valeurs pour les signaux de sortie et les signaux internes.

Pour améliorer la lisibilité, et lorsque vous aurez acquis de l’expérience, vous pourrez regrouper dans un même processus la gestion des mémorisations et les fonctions combinatoires situées en amont des registres. Dans l’architecture Compact ci-dessous, le calcul de la valeur suivante de a_reg et b_reg a été intégré dans le processus p_ab_reg :

architecture Compact of GCD is
    signal a_reg, b_reg : positive;
begin
    p_ab_reg : process(clk_i)
    begin
        if rising_edge(clk_i) then
            if load_i = '1' then
                a_reg <= a_i;
                b_reg <= b_i;
            elsif a_reg > b_reg then
                a_reg <= a_reg - b_reg;
            elsif b_reg > a_reg then
                b_reg <= b_reg - a_reg;
            end if;
        end if;
    end process p_ab_reg;

    g_o    <= a_reg;
    done_o <= '1' when a_reg = b_reg else '0';
end Compact;