Instructions concurrentes

Les instructions concurrentes doivent être placées dans le corps d’une architecture. Elles s’exécutent en parallèle et communiquent à travers des signaux. L’ordre dans lequel les instructions concurrentes sont écrites n’a pas d’influence sur le comportement de l’architecture.

Affectation de signal

Une affectation concurrente de signal calcule le résultat d’une expression et affecte la valeur obtenue au signal de destination. Cette instruction est exécutée à chaque fois que l’un des signaux dont elle dépend change de valeur.

Affectation simple

nom de signal <= expression;

Ici, nom de signal peut également désigner un élément de tableau ou un champ d’une valeur structurée.

En VHDL, le symbole <= peut avoir deux significations :

Par exemple, l’instruction ci-dessous se lit : « a reçoit '1' si la valeur de b est inférieure ou égale à la valeur de c, sinon a reçoit '0' ».

a <= '1' when b <= c else '0';

Affectation conditionnelle

L’affectation concurrente conditionnelle choisit l’expression à évaluer parmi une liste d’expressions associées à des conditions. Le signal de destination reçoit le résultat de la première expression dont la condition est vraie. Si aucune condition n’est vraie, la clause else finale est évaluée.

nom de signal <= expression when condition
            else expression when condition
            ...
            else expression;

Les conditions sont des expressions dont le résultat doit être de type boolean.

D’un point de vue syntaxique, la clause else finale est facultative.

En pratique, nous considérerons son absence comme une erreur de conception pour les raisons évoquées dans la section Bascules et registres.

Affectation avec sélection

L’affectation concurrente avec sélection choisit l’expression à évaluer parmi une liste d’expressions, en fonction de la valeur d’une expression de référence.

with expression select
   nom de signal <= expression when valeur,
                    expression when valeur | ... valeur,
                    ...
                    expression when others;

Après chaque mot-clé when, il est possible d’indiquer une valeur seule ou une liste de valeurs séparées par des barres verticales (|, à ne pas confondre avec l’opérateur or). La clause when others est facultative : elle permet de traiter tous les cas non couverts dans les clauses when précédentes.

Les clauses when doivent être mutuellement exclusives (on ne doit pas trouver deux fois la même valeur) et exhaustives (toutes les valeurs possibles du signal de référence doivent être couvertes).

Processus

Un processus est une instruction concurrente qui contient un bloc d’instructions séquentielles. Cela signifie que les instructions situées dans le corps d’un processus s’exécutent l’une après l’autre dans l’ordre indiqué.

nom du processus : process(nom de signal, ... nom de signal)
   déclaration
   ...
   déclaration
begin
   instruction séquentielle
   ...
   instruction séquentielle
end process nom du processus;

Le nom du processus est facultatif.

Les déclarations situées avant le mot-clé begin sont locales au processus. On pourra y trouver des déclarations de variables, de constantes, de types, mais pas de signaux.

La liste des signaux entre parenthèses est la liste de sensibilité du processus. À chaque fois que l’un de ces signaux change de valeur, le processus s’exécute à nouveau.

À partir du standard VHDL-2008, ont peut utiliser le mot-clé all comme liste de sensibilité à la place d’une liste de noms de signaux.

Ce remplacement est toujours possible pour les processus qui décrivent des circuits combinatoires. Les outils de développement sont aujourd’hui capables de déterminer la liste de sensibilité automatiquement.

process(all) -- équivalent à process(x, y)
begin
    if x < y then
        min <= x;
        max <= y;
    else
        min <= y;
        max <= x;
    end if;
end process;

Pour des circuits séquentiels, la façon dont le corps du processus est écrit peut être incompatible avec l’utilisation du mot-clé all. Par exemple, le processus ci-dessous est une description possible d’une bascule D. Il s’exécute à chaque fois que clk change, et copie d dans q si clk vaut '1'.

process(clk)
begin
    if clk = '1' then
        q <= d;
    end if;
end process;

En remplaçant la liste de sensibilité par all, l’instruction q <= d serait aussi exécutée à chaque modification de n’importe quel autre signal pendant la durée où clk vaut '1'. Un version correcte consiste à modifier la condition pour détecter les changements de clk :

process(all)
begin
    if rising_edge(clk) then
        q <= d;
    end if;
end process;

Instanciation

Une instruction d’instanciation crée un exemplaire (une instance) d’une entité en associant des valeurs à ses paramètres génériques, et en connectant des signaux à ses ports.

Instanciation d’une entité

Cette instruction a été introduite dans le standard VHDL-93. Si vous utilisez un outil qui n’accepte que le VHDL-87, vous devrez utiliser une instanciation de composant.

Selon la présence ou non de paramètres génériques dans l’entité à instancier, l’instruction d’instanciation peut se présenter sous l’une des formes suivantes :

nom de l'instance : entity nom de bibliothèque.nom d'entité(nom d'architecture)
    port map(associations de ports);

nom de l'instance : entity nom de bibliothèque.nom d'entité(nom d'architecture)
    generic map(associations de paramètres)
    port map(associations de ports);

Le « ; » indique la fin de l’instruction. Il n’y a pas de « ; » entre les clauses port map() et generic map().

Dans la définition ci-dessus :

La clause generic map() affecte une valeur à chaque paramètre générique de l’entité instanciée. La clause port map() établit les connexions entre les ports de l’instance et des signaux ou des valeurs.

Les associations de paramètres et de ports peuvent se présenter sous deux formes. Dans la notation par position, on donne simplement une liste d’expressions ou de noms de signaux qui sont associés aux paramètres et aux ports de l’instance en respectant l’ordre de leurs déclarations :

generic map(expression statique, ... expression statique)
port map(nom de signal ou expression statique, ... nom de signal ou expression statique)

Dans la notation par association, on indique explicitement les noms des paramètres ou des ports (à gauche des flèches) et les expressions ou signaux qui leur sont associés (à droite des flèches) :

generic map(
    nom de paramètre => expression statique,
    ...
    nom de paramètre => expression statique
)

port map(
    nom de port => nom de signal ou expression statique,
    ...
    nom de port => nom de signal ou expression statique
)

Une expression statique est une expression dont la valeur peut être précalculée au moment de la construction du circuit. Une telle expression contient typiquement des valeurs littérales et des constantes, mais pas de signaux.

À partir du standard VHDL-2008, une clause port map() peut contenir des expressions faisant intervenir des signaux.

Dans une association de port, on peut utiliser le mot-clé open pour indiquer qu’un port n’est pas connecté.

Voici un exemple inspiré de l’activité Instanciation :

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;

entity CounterDemo is
    port(
        clk_i        : in  std_logic;
        btn_center_i : in  std_logic;
        ...
    );
end CounterDemo;

architecture Structural of CounterDemo is
    signal sec_inc   : std_logic;
    signal sec_value : integer range 0 to 15;
    ...
begin
    -- Instanciation avec notation par position.
    div_counter_inst : entity work.CounterModN(Behavioral)
        generic map(100e6)
        port map(clk_i, '0', '1', open, sec_inc);

    -- Instanciation avec notation par association.
    sec_counter_inst : entity work.CounterModN(Behavioral)
        generic map(
            N => 16
        )
        port map(
            clk_i   => clk_i,
            reset_i => btn_center_i,
            inc_i   => sec_inc,
            value_o => sec_value,
            cycle_o => open
        );

    ...
end Structural;

La flèche => signifie « est relié à ».

Ne pas confondre :

Instanciation d’un composant

L’instanciation de composant est plus générale, mais également plus verbeuse car elle nécessite de déclarer un composant avec la même interface que l’entité à instancier. Nous ne détaillerons pas ici les raisons qui pourraient vous amener à la choisir, mais nous la mentionnons car elle figure encore dans de nombreux exemples respectant le standard VHDL-87, et elle est parfois imposée par les outils de développement. En pratique, dans des projets de taille modeste, l’instanciation d’entité est plus naturelle.

Selon la présence ou non de paramètres génériques dans le composant à instancier, l’instruction d’instanciation peut se présenter sous l’une des formes suivantes :

nom de l'instance : nom de composant
   port map(associations de ports);

nom de l'instance : nom de composant
   generic map(associations de paramètres)
   port map(associations de ports);

Génération de matériel

Génération conditionnelle

L’instruction if...generate permet d’ajouter ou de retirer un groupe d’instructions concurrentes d’une architecture en fonction de la valeur d’une condition. La condition doit être une expression statique qui retourne un booléen.

label : if condition generate
    instruction concurrente
    ...
    instruction concurrente
end generate label;

Ne pas confondre l’instruction if...generate avec l’instruction if ou l’affectation conditionnelle.

Ces dernières agissent sur le comportement du circuit, en lui permettant de prendre des décisions en fonction de conditions qui peuvent varier au cours du fonctionnement.

L’instruction if...generate agit sur la structure du circuit. Au moment de sa construction, un circuit différent sera réalisé selon que la condition est vraie ou fausse.

L’exemple ci-dessous décrit un circuit qui additionne la valeur absolue de ses opérandes.

entity AddAbs is
    generic(
        MIN, MAX : integer
    );
    port(
        a_i, b_i : in  integer range MIN to MAX;
        r_o      : out integer range 0 to 2 * MAX
    );
end AddAbs;

architecture Behavioral of AddAbs is
    signal abs_a, abs_b : integer range 0 to MAX;
begin
    pos_gen : if MIN >= 0 generate
        abs_a <= a_i;
        abs_b <= b_i;
    end generate pos_gen;

    neg_gen : if MIN < 0 generate
        abs_a <= a_i when a_i >= 0 else -a_i;
        abs_b <= b_i when b_i >= 0 else -b_i;
    end generate neg_gen;

    r_o <= abs_a + abs_b;
end Behavioral;

À partir du standard VHDL-2008, l’instruction if...generate accepte les clauses elsif...generate et else generate sur le modèle de l’instruction if.

abs_gen : if MIN >= 0 generate
    abs_a <= a_i;
    abs_b <= b_i;
else generate
    abs_a <= a_i when a_i >= 0 else -a_i;
    abs_b <= b_i when b_i >= 0 else -b_i;
end generate abs_gen;

VHDL-2008 définit également un instruction case...generate sur le modèle de l’instruction case.

Génération itérative

L’instruction for...generate permet de construire une architecture par répétition d’un groupe d’instructions concurrentes.

label : for nom du compteur in intervalle generate
    instruction concurrente
    ...
    instruction concurrente
end generate label;

L’exemple suivant décrit un circuit additionneur sur N bits réalisé avec N additionneurs sur 1 bit :

entity BitAdder is
    port(
        a_i, b_i, c_i : in  std_logic;
        s_o, c_o      : out std_logic
    );
end BitAdder;

entity VectorAdder is
    generic(
        N : positive
    )
    port(
        a_i, b_i : in  std_logic_vector(N - 1 downto 0);
        c_i      : in  std_logic;
        s_o      : out std_logic_vector(N downto 0)
    );
end VectorAdder;

architecture Structural of VectorAdder is
    signal c : std_logic_vector(0 to N);
begin
    c(0) <= c_i;

    add_gen : for k in 0 to N - 1 generate
        add_inst : entity work.BitAdder
            port map(
                a_i => a_i(k),
                b_i => b_i(k),
                c_i => c(k),
                s_o => s_o(k),
                c_o => c(k + 1)
            );
    end generate add_gen;

    s_o(N) <= c(N);
end Structural;

La boucle for...generate ci-dessus construit NN instances distinctes de l’entité BitAdder. L’utilisation de cette boucle impose la création d’un signal c de type vecteur qui rassemble les retenues en entrée et en sortie de chaque instance.

Ne pas confondre l’instruction concurrente for...generate et la boucle for.

L’instruction for...generate est une instruction concurrente qui contient d’autres instructions concurrentes. Elle agit sur la structure du circuit en construisant NN exemplaires d’un même groupe d’instructions.

La boucle for est une instruction séquentielle qui contient d’autres instructions séquentielles. Elle agit sur le comportement du circuit en décrivant une séquence répétitive d’opérations.