VHDL propose 3 principales instructions concurrentes :
Les instanciations de composants
les affectations concurrentes de signaux, qui peuvent être conditionnelles
Les processus, qui offrent la possibilité d'utiliser des instructions séquentielles.
Nous avons déjà manipulé les deux premiers types, Cette séance va nous permettre de maîtriser le troisième!
Par construction même d'un circuit électronique, les différentes structures d'un programme vhdl s'exécutent en parallèle les unes des autres. VHDL définit également la notion de tâches parallèles. Ces tâches sont appelées process. Toutes les instructions concurrentes sont en fait des process mais la déclaration explicite de process par le mot clé process permet de construire sa propre instruction par le biais d'instructions séquentielles internes au process. Un process peut avoir des variables locales.
Un processus est vu comme une boucle infinie, lorsqu'il arrive à la fin du code, il reprend automatiquement au début. L'ordre des instructions à l’intérieur du PROCESS est ici respecté à la différénce de l'architecture où il n'est pas pris en compte! On observe trois phases du PROCESS : veille, activation, exécution
Un processus peut être sensible à des points d'arrêt de façon à le synchroniser. La synchronisation est indiquée par un point d'arrêt qui est un événement particulier. Il existe 2 types de points d'arrêts :
Le processus est associé à une "liste de sensibilité" qui contient une liste de signaux qui réveillent le processus lors d'un changement d'un des signaux. Le processus est placé en veille si aucun signaux ne changent d'état ( '0' ou '1'...) à chaque début d'exécution de la boucle infinie. Le changement d'état peut alors être testé sur un signal S de la liste au sein du process. Sa syntaxe est S'event.
Le processus autorise des instructions d'arrêt wait dans sa description interne. Le wait est sensible soit à un signal soit à un temps physique. Il n'est pas recommandé d'utiliser le wait pour synthétiser un circuit. Dans nos TPs nous utiliserons le wait exclusivement pour la génération de programmes de simulation, en utilisant cette notion de temps physique afin de produire un stimuli pour le circuit testé...
Il existe des variables utilisables dans les process. Les variables sont internes au processus et sont affectées immédiatement, contrairement aux signaux qui eux ne sont pas affectés directement mais mis à jour en fin de processus avec la nouvelle valeur. C'est vraiment différent de la programmation en C ou Java ici !!!!!
Lu sur Wikipedia
Le VHDL a deux aspects qui peuvent être contradictoires. Lorsqu'il s'agit d'écrire un modèle comportemental qui sera simplement simulé, le langage est compilé puis exécuté par le simulateur. Par contre lorsqu'il s'agit de décrire un circuit qui sera créé par un synthétiseur, la philosophie est sensiblement différente. L'outil de synthèse, devant transformer l'ensemble du code fourni en une implémentation à base de portes logiques, est conçu pour fonctionner de manière très cadrée. Il est nécessaire de pouvoir lui fournir une description claire (dont la synthèse correspond à l'architecture recherchée) tout en étant le moins spécifique possible (afin de permettre à l'outil d'optimiser au maximum le circuit généré).
Par exemple, si l'on désire générer une fonction de logique combinatoire (indépendante de toute horloge), il faudra affecter l'ensemble des sorties à chaque appel du process, sans quoi l'outil de synthèse, considérant que les sorties non assignées conservent leur ancienne valeur, placera des bascules D en sortie de chaque sortie non affectée. Cette solution est alors très mauvaise, puisqu'elle transforme la fonction en une fonction de logique séquentielle, donc dépendant d'une horloge (qui de plus est spécifiée par l'outil de synthèse, hors de contrôle du concepteur).
Cette différence implique un grand travail en amont et en aval du codage, le circuit décrit doit avoir déjà été pensé avant d'être codé et il doit être vérifié après conception, en considérant le nombre de portes et les caractéristiques d'implantation, afin de s'assurer qu'aucune erreur de description n'est présente. Ces contraintes très fortes sur le programmeur entraînent l'adoption de guides de conduites et de méthodes de codage très strictes.
Ces grandes différences avec un langage de programmation comme le C font du VHDL un langage à part, plus proche de l'électronique que de l'informatique. Il n'est d'ailleurs pas rare de voir implémenté sur des FPGA des architectures de micro-contrôleurs, eux-mêmes programmés en assembleur ou en C dans la suite du projet.
Instructions conditionnelles
Un exemple simple de process consiste à décrire une fonction logique, il suffit de ne pas utiliser d'horloge ( on verra çà la semaine prochaine ) et de relancer le process chaque fois qu'une des entrées de la table de vérité change d'état! on utilise la liste de sensibilité. Voici un code VHDL :
ENTITY mux4_1 IS
PORT (e3, e2, e1, e0 : IN BIT;
c : IN INTEGER RANGE 0 TO 3;
s : OUT BIT);
END mux4_1;
ARCHITECTURE archi1 OF mux4_1 IS
BEGIN
PROCESS(c, e0, e1, e2, e3)
BEGIN
IF (c = 0) THEN
s <= e0;
ELSIF (c = 1) THEN
s <= e1;
ELSIF (c = 2) THEN
s <= e2;
ELSE
s <= e3;
END IF;
END PROCESS;
END archi1;
Le IF THEN ELSE est un constructeur séquentiel uniquement accepté dans une code séquentiel c'est à dire un process!!!!! La liste de sensibilité active le process dès qu'un au moins des 5 signaux en entrée change d'état.
Le second constructeur séquentiel que nous utiliserons particulièrement est le CASE. C'est comme pour les langages informatiques que vous connaissez, il permet de réaliser un choix multiple. Reprenons le code du mux4:
ENTITY mux4_1 IS
PORT (e3, e2, e1, e0 : IN BIT;
c : IN INTEGER RANGE 0 TO 3;
s : OUT BIT);
END mux4_1;
ARCHITECTURE archi1 OF mux4_1 IS
BEGIN
PROCESS(c, e0, e1, e2, e3)
BEGIN
CASE c IS
WHEN 0 => s <= e0;
WHEN 1 => s <= e1;
WHEN 2 => s <= e2;
WHEN 3 => s <= e3;
END CASE;
END PROCESS;
END archi1;
Le résultat de synthèse Vivado sera exactement le même , VIVADO a reconnu un multiplexeur depuis le code VHDL et utilise un composant matériel de sa bibliothèque. Comme pour l'exemple avec le IF THEN ELSE, il génère ensuite un Lut6. Je l'ai testé pour vous :
Remarques concernant les tests (avec IF ... THEN ou CASE):
il faut lister tous les cas possibles, sinon un composant matériel de mémorisation est créé. En utilisant le même code Mux4 mais en supprimant le cas c=3, certains compilateurs ont automatiquement inséré un Latch ( Verrou) asynchrone: çà ne va pas être très bon sur un FPGA synchrone :-( Vivado lui produit une erreur de synthèse. Il faut mieux définir un Mux 4 complet et ne pas utiliser le quatrième cas ... Idem dans in IF il faut mieux finir par un ELSE que par un ELSIF qui lui générera un Latch même en Vivado :-(
pour le CASE il est possible d’utiliser la syntaxe WHEN OTHERS => ... pour donner une valeur par défaut à tous les cas restant.
on peut imbriquer les IF et les CASE
on peut tester
des INTEGER (exemple: IF c = 3 -- base 10
IF c = 16#13# -- base 16)
des BIT (exemple: IF s = ‘1’)
des BUS (exemples: IF s = "1100" -- base 2 sur 4 bits
IF s = X"2C" -- hexa sur 8 bits )
Remarque concernant les PROCESS
il est possible de définir plusieurs PROCESS dans une même architecture; ils sont alors indépendants et concurrents, ils se trouvent dans la partie concurrente de l’architecture. Chacun se synthétise en un circuit indépendant des autres, mais des signaux peuvent les relier. En particulier 2 process synchronisés sur la même clock peuvent s’échanger des signaux entre deux tops successifs.
Reprenez le code de la semaine dernière, cette fois définissez l'architecture avec un process
ARCHITECTURE Archi1exo4 OF exo4 IS
BEGIN
s <= ( a AND NOT adr(1) AND NOT adr(0) )
OR ( b AND NOT adr(1) AND adr(0) )
OR ( c AND adr(1) AND NOT adr(0) )
OR ( d AND adr(1) AND adr(0) );
END Archi1exo4;
Donnez le code avec un process du décodeur pour l'affichage 7 segments
a. Produisez avec Vivado le RTL schematic de ce code VHDL suivant :
library ieee;
use ieee.std_logic_1164.all;
entity test is
port ( A : in std_logic_vector(1 downto 0);
B, C, D, E : in std_logic;
F, G, H : out std_logic);
end test;
architecture synthesizable of test is
begin
process (A, B, C, D, E)
begin
case A is
when "00" => F <= B; H <= B;
when "01" => F <= C; G <= B; H <= C;
when "10" => G <= B; H <= D;
when "11" => H <= E;
when others => null;
end case;
end process;
end synthesizable;
b. En quoi le code est-il inefficace? Écrivez une autre version de ce circuit en VHDL, en utilisant un processus séparé pour chaque signal de sortie F G et H..
c. Avec le code obtenu vérifiez sur Vivado que le circuit généré ressemble à celui qui vous avez obtenu à la question a.
Une variable sert à effectuer un calcul intermédiaire et permet au compilateur de générer la logique correspondante, elle ne correspond à rien de physique. Elles servent à manipuler des variables temporaires/intermédiaires pour faciliter le développement d'un algorithme séquentiel. A la différence des signaux, les variables sont toujours locales à un processus: il n’y a pas de variables globales... Voici un exemple de code qui permet d'exprimer l’algorithme séquentiel qui permet de compter le nombre de bits à '1' dans un mot de 3 bits. On voit l'importance de la mise à jour immédiate de la variable nombre1, un signal aurait été mis à jour à la fin du process! On aurait pu en donner sa table de vérité et donc en produire la structure de circuit directement.
ENTITY compte_1 IS
PORT (e : IN STD_LOGIC_VECTOR(2 DOWNTO 0);
s : OUT INTEGER RANGE 0 TO 3 );
END compte_1;
ARCHITECTURE combinatoire OF compte_1 IS
BEGIN
PROCESS(e)
VARIABLE nombre_1 : INTEGER;
BEGIN
nombre_1 := 0;
IF e(2) = '1' THEN
nombre_1 := nombre_1 +1;
END IF;
IF e(1) = '1' THEN
nombre_1 := nombre_1 +1;
END IF;
IF e(0) = '1' THEN
nombre_1 := nombre_1 +1;
END IF;
s <= nombre_1;
END PROCESS;
END combinatoire ;
Effectivement la variable nombre_1 n’est pas mémorisée dans le circuit synthétisé par Vivado! Il n'y a que deux incrémenteurs et trois multiplexeurs 2x1!
Comme dans les langages de programmation classiques, elles servent à effectuer une description itérative et donc séquentielle (simplifie l’écriture). Elles n'existent que dans les process! Elles ne correspondent à rien de physique, on peut utiliser une variable de boucle pour décrire l’algorithme. Il en existe plusieurs formes:
Boucle simple : [label :] loop instructions; end loop[ label]; il existe un exit pour sortir de la boucle, un next pour passer à l'itération suivante.
Boucle while : [label :] while expression loop instructions; end loop[ label];
Boucle for : [label :] for var in exp1 to exp2 loop instructions; end loop[ label];
Un complément à lire ici.
Voici une vidéo rapide qui vous montre comment utiliser un for pour réaliser un décodeur:
A : Reprendre le code exo2 TP2 avec un process utilisant une variable
B : Voici un code vhdl qui utilise un FOR
library ieee;
use ieee.std_logic_1164.all;
entity DEC is
port( E: in std_logic_vector(1 downto 0);
S: out std_logic_vector(3 downto 0));
end;
architecture FLOT_MUX of DEC is
begin
process (E)
variable N : integer;
begin
N := 0;
if E(0) = '1' then
N := N + 1;
end if;
if E(1) = '1' then N := N + 2; end if;
S <= "0000";
for I in 0 to 3 loop
if I = N then
S(I) <= '1';
end if;
end loop;
end process;
end FLOT_MUX;
Que fait ce code? Produire le RTL shematic avec Vivado.
Modifiez le process afin d’éliminer la variable integer. Un case sur E devrait faire l'affaire! Que devient le RTL schematic?
Modifiez ce dernier process afin de prendre en compte une nouvelle entrée enable qui valide le circuit s'il vaut '1' sinon toutes les sorties de S sont à '0'
Proposez un code qui implémente la même chose qu'en 3, avec un with select et donc sans process. Comparez le RTL produit par Vivado, Qu'en pensez vous?