Processus

Définition

Dans certaines situations, les affectations concurrentes peuvent être inadaptées à la description du comportement d’un circuit complexe. Il est parfois plus naturel de représenter ce comportement sous la forme d’une séquence d’instructions.

En VHDL, une architecture peut contenir des processus. Chaque processus contient un bloc d’instructions qui s’exécutent dans l’ordre. Les processus fonctionnent en parallèle avec les autres instructions concurrentes de l’architecture.

Cette page illustre la notion de processus à travers des exemples. Le détail de la syntaxe est expliqué dans les sections :

Exemples

Calcul de PGCD

Dans l’exemple ci-dessous, nous reprenons l’exemple du PGCD en remplaçant les instructions d’affectation concurrentes conditionnelles par un unique processus avec une instruction if.

architecture WithProcesses 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 front montant de clk
    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 done_o et de la valeur suivante de a_reg et b_reg
    p_ab_next_done_o : process(load_i, a_i, b_i, a_reg, b_reg)
    begin
        if load_i = '1' then
            a_next <= a_i;
            b_next <= b_i;
            done_o <= '0';
        elsif a_reg > b_reg then
            a_next <= a_reg - b_reg;
            b_next <= b_reg;
            done_o <= '0';
        elsif b_reg > a_reg then
            a_next <= a_reg;
            b_next <= b_reg - a_reg;
            done_o <= '0';
        else
            a_next <= a_reg;
            b_next <= b_reg;
            done_o <= '1';
        end if;
    end process p_ab_next_done_o;

    -- Affectation du résultat
    g_o <= a_reg;
end WithProcesses;

Cette description contient deux processus nommés p_ab_reg et p_ab_next_done_o. Le premier décrit des registres synchronisés sur les fronts montants de clk_i. Le second décrit de la logique combinatoire.

Après le mot-clé process, la liste des signaux entre parenthèses est la liste de sensibilité du processus. La ligne ci-dessous signifie que le processus p_ab_next_done_o doit s’exécuter à chaque fois que l’un des signaux load_i, a_i, b_i, a_reg ou b_reg change de valeur.

p_ab_next_done_o : process(load_i, a_i, b_i, a_reg, b_reg)

Même si les instructions de ce processus sont supposées s’exécuter dans l’ordre, la durée totale du processus est considérée comme nulle. Si le même signal est affecté plusieurs fois dans la même séquence d’instructions, seule la dernière affectation aura un effet.

Voici une autre manière d’écrire ce processus :

-- Calcul de done_o et de la valeur suivante de a_reg et b_reg
p_ab_next_done_o : process(load_i, a_i, b_i, a_reg, b_reg)
begin
    -- Cas par défaut
    a_next <= a_reg;
    b_next <= b_reg;
    done_o <= '0';

    if load_i = '1' then
        a_next <= a_i;
        b_next <= b_i;
    elsif a_reg > b_reg then
        a_next <= a_reg - b_reg;
    elsif b_reg > a_reg then
        b_next <= b_reg - a_reg;
    else
        done_o <= '1';
    end if;
end process p_ab_next_done_o;

Décodeur 7 segments

Réécrivons le décodeur 7 segments en remplaçant l’instruction with-select par un processus avec une instruction case. Les deux versions décrivent le même comportement et représentent le même circuit.

architecture WithProcess of SegmentDecoder is
begin
    p_segments_o : process(digit_i)
    begin
        case digit_i is
            when 0 =>
                segments_o <= "1111110";
            when 1 =>
                segments_o <= "0110000";
            when 2 =>
                segments_o <= "1101101";
            when 3 =>
                segments_o <= "1111001";
            when 4 =>
                segments_o <= "0110011";
            when 5 =>
                segments_o <= "1011011";
            when 6 =>
                segments_o <= "1011111";
            when 7 =>
                segments_o <= "1110000";
            when 8 =>
                segments_o <= "1111111";
            when 9 =>
                segments_o <= "1111011";
            when 10 =>
                segments_o <= "1110111";
            when 11 =>
                segments_o <= "0011111";
            when 12 =>
                segments_o <= "1001110";
            when 13 =>
                segments_o <= "0111101";
            when 14 =>
                segments_o <= "1001111";
            when 15 =>
                segments_o <= "1000111";
        end case;
    end process p_segments_o;
end WithProcess;

Compter le nombre de bits à '1' dans un vecteur

La carte Basys3 est équipée d’une rangée d’interrupteurs à glissière. Dans cet exemple, nous souhaitons afficher sur un afficheur 7 segments le nombre d’interrupteurs en position haute. Comme le décodeur 7 segments ne peut afficher que des nombres entre 0 et 15, nous afficherons un 0 et nous allumerons un point lorsque les 16 interrupteurs seront actifs. L’entité se présentera de la manière suivante :

entity NumberOfSwitchesUp is
    port(
        switches_i        : in  std_logic_vector(15 downto 0);
        disp_segments_n_o : out std_logic_vector(0 to 6);
        disp_point_n_o    : out std_logic;
        disp_select_n_o   : out std_logic_vector(3 downto 0)
    );
end NumberOfSwitchesUp;

Dans l’architecture, un signal count de type entier indiquera le nombre de bits à '1' dans le vecteur switches_i. Comme dans l’exemple CounterDemo, nous instancions un décodeur 7 segments pour afficher ce nombre et nous sélectionnons l’afficheur le plus à droite.

architecture Behavioral of NumberOfSwitchesUp is
    signal count    : integer range 0 to 16;
    signal digit    : integer range 0 to 15;
    signal segments : std_logic_vector(0 to 6);
begin
    -- Affichage du nombre sur l'afficheur le plus à droite
    decoder_inst : entity work.SegmentDecoder(TruthTable)
        port map(
            digit_i    => digit,
            segments_o => segments
        );

    -- Afficher 0 si count dépasse 15.
    digit <= 0 when count > 15 else count;

    disp_segments_n_o <= not segments;
    disp_point_n_o    <= '0' when count > 15 else '1';
    disp_select_n_o   <= "1110";

    -- Comptage du nombre de bits à '1' dans le vecteur switches_i
    ...
end Behavioral;

Pour seize interrupteurs, il y a 65536 combinaisons possibles d’états. En principe, rien n’interdit d’écrire toutes ces combinaisons dans une instruction with-select ou case mais ça ne paraît pas raisonnable. Utilisons plutôt une boucle for pour parcourir le vecteur switches_i et incrémenter une variable à chaque fois que nous rencontrons un '1' :

architecture Behavioral of NumberOfSwitchesUp is
    signal count    : integer range 0 to 16;
    ...
begin
    ...

    -- Comptage du nombre de bits à '1' dans le vecteur switches_i
    p_count : process(switches_i)
        variable n : integer range 0 to 16;
    begin
        n := 0;
        for k in 0 to 15 loop
            if switches_i(k) = '1' then
                n := n + 1;
            end if;
        end loop;
        count <= n;
    end process p_count;
end Behavioral;

La variable n est locale au processus p_count. Elle ne sert qu’à conserver des valeurs intermédiaires nécessaires au calcul du signal count.

Signaux et variables

Dans une architecture les instructions concurrentes réagissent aux variations des signaux auxquelles elles sont sensibles. À chaque fois qu’une instruction concurrente met à jour un signal, cela peut déclencher l’exécution d’autres instructions concurrentes. On peut ainsi assister à des séries de réactions en chaînes jusqu’à ce que tous les signaux se soient stabilisés.

Pour mettre en évidence l’enchaînement des mises à jour de signaux en distinguant clairement les causes et les effets, on considère que les affectations de signaux prennent toujours effet avec un petit retard. Ce retard est appelé délai delta. Il ne représente pas la durée réelle des calculs dans le circuit. Dans un simulateur VHDL, il est même considéré comme infiniment petit.

Dans les instructions d’affectation concurrentes

Considérons l’exemple de la fonction majorité étudié précédemment, dans sa version avec un multiplexeur à deux voies.

architecture Behavioral of Majority is
    signal bnc, brc : std_logic;
begin
    m_o <= bnc when a_i = '0' else brc;
    bnc <= b_i and c_i;
    brc <= b_i or  c_i;
end Behavioral;

Dans cette architecture, si le signal b_i prend une nouvelle valeur à la date tt, alors :

Dans les processus

L’affectation d’un signal dans un processus obéit exactement aux mêmes règles. Si un processus est déclenché à la date tt, alors, au cours de l’exécution de ce processus :

Considérons, par exemple, le processus suivant :

-- Ce processus ne fonctionne pas !
p_count : process(switches_i)
begin
    count <= 0;
    for k in 0 to 15 loop
        if switches_i(k) = '1' then
            count <= count + 1;
        end if;
    end loop;
end process p_count;

Si nous annotons ce processus en indiquant à quelles dates s’effectuent les lectures et écritures de signaux, nous obtenons :

p_count : process(switches_it)
begin
    count{t + δ} <= 0;
    for k in 0 to 15 loop
        if switches_i{t}(k) = '1' then
            count{t + δ} <= count{t} + 1;
        end if;
    end loop;
end process p_count;

Ainsi, nous obtiendrons les deux effets suivants :

Variables

Les variables servent à conserver des valeurs intermédiaires utilisées localement dans une séquence d’instructions. Elles ne servent pas à transporter des données entre des instructions concurrentes. Par conséquent, elles ne peuvent pas déclencher l’exécution d’autres instructions concurrentes. Elles ne sont pas soumises à un délai delta.

Le processus ci-dessous illustre l’utilisation d’une variable :

p_count : process(switches_i)
    variable n : integer range 0 to 16
begin
    n := 0;
    for k in 0 to 15 loop
        if switches_i(k) = '1' then
            n := n + 1;
        end if;
    end loop;
    count <= n;
end process p_count;