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 :
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;
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;
'1'
dans un vecteurLa 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
.
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.
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 , alors :
bnc
et brc
seront mis à jour à la date ,m_o
sera mis à jour à la date .L’affectation d’un signal dans un processus obéit exactement aux mêmes règles. Si un processus est déclenché à la date , 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 :
count <= count + 1;
lira toujours la même valeur du signal count
, prise à la date où le
processus s’est déclenché.count
sera effectivement réalisée.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;