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.
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.
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.
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
.
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.
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.
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
.
load_i
vaut '1'
, les entrées a_i
et b_i
sont copiées dans a_reg
et b_reg
.load_i = '0'
), les registres sont mis à jour à chaque front d’horloge
en soustrayant la plus petite valeur de la plus grande.done_o
vaut '1'
lorsque les deux registres sont égaux.
Le résultat est la valeur du signal a_reg
.Un schéma du circuit correspondant est proposé ci-dessous.
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;