FPGA Et Le VHDL | Apprendre Et Comprendre
Maybe your like
Introduction
L’auteur a fait le choix de vous faire partager un document sur les FPGA et de vous parler du VHDL.
Toujours dans un soucis de partage, l’auteur a joint d’autres extraits réalisés par d’autres personnes. Les noms sont laissés intentionnellement.
presentation_fpga
- Ce qu’est le VHDL
- Présentation
Le Vhic Hardware Description Language (VHIC = Very High Speed Integrated Circuit) a été normalisé en 1987 par l’IEEE, sous la norme IEEE 1076-87. Une importante évolution est parue en 1993, sous la norme IEEE 1076-93. C’est cette version du langage qui est majoritairement supportée par les outils du marché.
D’autre révision de la norme sont parues depuis, mais ce sont des évolutions « mineures », et pas forcement supportés par les logiciels.
Le VHDL est un langage de description matériel, ce n’est absolument un langage « software » comme le C ou le Java.
A partir de ce langage, on peut définir un système par une structure hiérarchique de fonctions, par une structure matérielle, en encore par une modélisation temporelle (même si elle n’est pas utilisable pour faire du code synthétisable).
Ce langage permet d’aller d’un niveau d’abstraction très élevée, par une description algorithmique, jusqu’à un niveau proche du matériel, où l’on décrit le système par un ensemble de porte logique et d’interconnexion (« gate level »). Entre les deux, se trouve le niveau RTL (Register Transfer Level), qui permet de définir le système par une architecture de type machine de Moore ou Mealy.
C’est le niveau RTL que l’on utilise le plus quand on fait de la synthèse. En effet, le niveau algorithmique n’est pas forcement synthétisable, il est plutôt utilisé pour faire du testbench ou de la simulation. Le niveau « gate level » est souvent lourd à décrire quand le système devient un tant soit peu complexe.
L’avantage du code RTL est qu’il est indépendant des technologies utilisées dans la cible à programmer, alors qu’au niveau « gate », il faut prendre en compte la techno du circuit cible, car tout n’est pas permis.
- Le flot de conception
fig 1 : Flot de conception
Le code à synthétiser sera généralement écrit dans un niveau RTL. Parallèlement, un banc de test doit être écrit.
Ce banc de test, écrit sous la forme d’une modélisation de l’environnement, servira à appliquer les stimuli d’entrée et à extraire les résultats, pour ensuite permettre la validation du système.
Une fois le code validée, on le synthétise grâce aux librairies du fabriquant, afin de générer la « netlist » et le code de type « porte ».
Ensuite, on fait le Placement/routage du circuit (ou le layout s’il s’agit d’un ASIC). Une fois le circuit routé, on en extrait les temps de propagation et l’on re-simule le système, pour vérifier que le placement routage est suffisamment optimisé pour respecter les contraintes de temps.
Si les contraintes de temps ne sont pas respectées, on relance un placement routage pour tenter une optimisation. Ce processus est itératif, et sera renouvelé un nombre de fois suffisant pour atteindre les contraintes. Dans le cas où les contraintes ne sont pas atteintes, le synthétiseur déclare alors qu’il ne peut router le circuit de façon a tenir les contraintes, et donne les chemins critiques.
- Introduction au langage
- les en-têtes de fichier
La première chose à faire est de définir les librairies qui seront utilisées dans le code.
Cela se fait de la manière suivante :
Tout d’abord, la librairie principale (en générale, IEEE).
ensuite, le mot clé « use », qui indique quelle package de la librairie nous allons utiliser. Après cela, le nom du package. Enfin, le .all signifie que l’on souhaite utiliser tout ce qui se trouve dans ce package. Lorsque le nom de la librairie est précédé de IEEE., cela signifie que c’est une librairie qui est définie dans la norme IEEE, et que l’on retrouvera donc normalement dans tout logiciel. A l’inverse, il faut se méfier des librairies qui ne sont pas IEEE, car elles sont en générale spécifiques à un logiciel.
Les librairies IEEE principales sont : • IEEE.standard • IEEE.std_logic_1164 • IEEE.numeric_std
- IEEE.std_logic_arith Attention, il ne faut pas utiliser les librairies numeric_std et std_logic_arith en même
temps : la librairie std_logic_arith est en fait une version améliorée de la numeric_std, développé par synopsys, et qui a été ensuite incorporé dans la norme IEEE. Le fait d’utiliser les 2 librairies en même temps causera un conflit lors de l’utilisation de certaines fonctions.
Library IEEE ; use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all;
- l’entité (entity)
Un design est tout d’abord défini par une déclaration d’entité. Cette entité permet de décrire l’interface avec l’environnement extérieur. On y retrouve donc un nom d’entité, et la liste des signaux, avec leurs caractéristiques (nom, mode, type).
Elle est définie de la manière suivante :
Un signal d’entrée/sortie est donc défini par :
- – son nom
- – son mode : in, out, inout
- – son type : std_logic, std_logic_vector(x downto x) (on verra les différents types par la
suite)
- L’architecture
Une fois l’entité définie, il faut décrire son fonctionnement, c’est ce que l’on fait avec l’architecture.
Une architecture fait toujours référence à une entité (ici, HALFADD). Elle est définie par un nom quelconque (ici RTL), que l’on pourra choisir pour expliciter la façon dont on code.
A la suite de la déclaration de l’architecture, on définira les déclarations préalables (signaux internes, composants), qui seront abordés un peu plus tard.
Ensuite, viens le mot clé « begin ». A sa suite, on trouvera le code qui décrit le fonctionnement de l’architecture.
Dans une architecture, toutes les lignes combinatoires ou bloc de process sont exécutées en parallèle.
Entity HALFADD is Port ( A, B : in std_logic ; SUM, CARRY : out std_logic );
End HALFADD;
Architecture RTL of HALFADD is
— Déclarations préalables
Begin SUM <= A xor B;
CARRY <= A and B; End RTL
- Le Process
Lorsque l’on a besoin de faire une exécution séquentielle dans une architecture, on utilise
Entity MUX is Port ( A, B, SEL : in std_logic; OP : out std_logic ); End MUX;
Architecture TEST of MUX is Begin MUXPROCESS : process (A, B, SEL) Begin
If (SEL = ‘1’) then OP <= A;
Else OP <= B;
End if; End process MUXPROCESS;
End TEST;
un process.
Un process peut être définie par un label, mais ce n’est pas obligatoire. Il y’a par contre toujours une liste de sensibilité (ce qui se trouve entre parenthèse). Lorsque l’un des signaux présent dans la liste de sensibilité va changer, le contenu du process est alors exécuté. Si l’on oublie de mettre un signal dans la liste de sensibilité, le résultat peut être complètement différent.
Dans le cas 1, sortie OP avec liste de sensibilité normale (A, B, SEL) Dans le cas 2, sortie OP avec liste de sensibilité sans le signal A
D’une manière générale, on mettra dans la liste de sensibilité tous les signaux qui seront scrutés dans le process.
- Les signaux les plus utilisées
5.1Librairie standard :
- Boolean (true ; false)
- Bit (‘0’ ; ‘1’)
- Bit_vector (vecteur de bit)
- Integer
- Real
- Character
- String (vecteur de charactère)
- Time (surtout en simulation)
5.2Librairie std_logic_1164
C’est la librairie que l’on utilise le plus désormais. Elle reprend les définitions de la norme IEEE1164, norme qui définie les signaux utilisées en milieu industrielle.
- Std_ulogic(‘U’;‘X’;‘0’;‘1’;‘Z’;‘W’,‘L’;‘H’;‘-‘)
- Std_logic : qui reprend les std_ulogic, mais qui gère les conflits de signaux lors
d’assignement multiple. C’est ce que l’on appel un type « résolue ».
- Std_ulogic_vector : vecteur de std_ulogic
- Std_logic_vector : vecteur de std_logic
Comme vous pouvez le voir, la norme 1164 définie beaucoup plus d’état pour un signal que la norme de base. C’est pourquoi on l’utilise aujourd’hui majoritairement, ne serait-ce que pour la possibilité d’utiliser un état ‘Z’ (haute impédance), très utile pour des bus de données.
Attention, le type bit n’est pas compatible avec le type std_(u)logic. Il faut impérativement utiliser des fonctions de conversion pour passer de l’un à l’autre.
5.3Librairie std_logic_arith
C’est la librairie utilisée lorsque l’on a besoin de travailler avec les types « unsigned » et « signed ». Ce sont des vecteurs de std_logic, avec lesquels on peut faire des opérations arithmétiques (‘+’ ; ‘-’ ; ‘*’ ; ‘/’ ; ‘=’) et de comparaison numérique (‘<’ ; ‘<=’ ; ‘>’ ; ‘>=’ ; ‘=’ ; ‘/=’).
Comme les types unsigned et signed sont des vecteurs de std_logic, on peut facilement faire des affectations vers le type std_logic_vector, en utilisant un « cast » (comme dans le langage C) :
Architecture test of entity_test is Signal A , B, C : std_logic_vector(5 downto 0) ; Signal D, E, F : unsigned(5 downto 0) ; begin D <= unsigned(A) ; E <= unsigned(B) ; F <= D+E ; — il est interdit de faire A+B directement avec des std_logic C <= std_logic_vector(F) ;
5.4Agrégats et concaténation sur les vecteurs
Dans le cas d’un vecteur qui doit recevoir plusieurs « sources » sur des portions différentes, on peut utiliser l’agrégat, plus synthétique qu’une affectation par segment et qui sera moins sujette a erreurs :
Signal A, B, C : std_logic ; Signal Z_bus : std_logic_vector(1 downto 0) ; Signal Status_byte : std_logic_vector(7 downto 0) ;
— affectation par position
Z_bus <= (A, B) ;
— affectation par association de nom
Status_byte <= (7 => ‘1’, 5 downto 4 => Z_bus , 6 => C, others => ‘0’) ;
— affectation par concaténation
Status_byte <= ‘1’ & C & Z_bus & « 0000 »
— affectation par segment
Status_byte(7) <= ‘1’ ; Status_byte(6) <= C ; Status_byte(5 downto 4) <= Z_bus ; Status_byte(3 downto 0) <= « 0000 » ;
Les agrégats et concaténation doivent toujours faire la même taille que celle du vecteur cible.
5.5Attributs des signaux et vecteurs
Vecteurs :
Si l’on prend un signal A défini de la manière suivante :
Signal A : std_logic_vector(5 downto 0) ;
A’high renvoie ‘5’ A’low renvoie ‘0’ A’left renvoie ‘5’ A’right renvoie ‘0’ A’range renvoi ‘5 downto 0’ A’reverse_range renvoi ‘0 to 5’ A’length renvoi ‘6’ A’ascending renvoie ‘false’
Signaux :
Si l’on prend un signal X défini de la manière suivante :
Signal X : std_logic ;
X’event renvoi ‘true’ quand X change de valeur
Il existe d’autre propriété pour les signaux, mais qui ne sont pas synthétisables, donc nous ne les aborderons pas ici.
TP CAO Electronique Numérique – 2005 Romain Berny
- Règles d’écriture
Le langage VHDL est très strict sur la syntaxe.
- Un signal doit être défini par son type et sa taille.
- Tout objet doit être déclaré avant d’être utilisé.
- Les noms de signaux, labels, etc. … ne doivent contenir que des lettres, nombres
et underscore. Ils doivent impérativement commencer par une lettre, et ne pas terminer par un underscore (certains logiciels ne le supporte pas, même si ce n’est explicite dans la norme).
- Le langage VHDL est case insensitive : un signal « abc » peut être appelé en tapant « Abc » ou « ABC ».
- Pour introduire des commentaires, on fait commencer le commentaire par » — « . Le commentaire se termine à la fin de la ligne, on ne peut pas faire de commentaires sur plusieurs lignes sans remettre à chaque fois les 2 traits.
- Il ne faut jamais écrire des choses qui vont créer un feedback zéro délai :
OP <= OP + Y ;
Cette écriture est syntaxiquement correcte, mais va être refusée par la plupart des synthétiseurs, et fera planter la majeure partie des simulateurs.
Pour réaliser cette fonction, il faut le faire dans un process :
Ici, pas de boucle zéro délai, car la nouvelle valeur d’OP ne sera positionnée qu’une fois le process exécuté, cela induit donc un délai de propagation.
- Il n’est pas recommandé de jouer sur les signaux présents ou non dans la liste de sensibilité pour essayer de faire des mémorisations implicite.
En effet, les synthétiseurs vont de toute façon réintégrer dans la liste de sensibilité tous les signaux oubliés. Ne pas le faire soit même dès le début présente deux risques :
- – la simulation et la synthèse risquent de ne pas avoir le même résultat.
- – le synthétiseur peut mal interpréter la mémorisation implicite espérée, et
synthétiser une solution matérielle complètement différente du résultat voulu.
Process (OP, Y) Begin OP <= OP + Y ; End process ;
III.L’écriture séquentielle et parallèle
- L’écriture séquentielle
Les fonctions suivantes ne peuvent être écrites qu’à l’intérieur d’un process :
1.1 If
La séquence ne sera exécutée que si la CONDITION est réalisé.
La séquence1 sera exécutée si la CONDITION est réalisé, sinon c’est la séquence2 qui sera exécutée.
If CONDITION then
— séquence d’opération
End if ;
If CONDITION then
— séquence1 d’opération
Else
— séquence2 d’opération
End if ;
If CONDITION1 then
— séquence1 d’opération
Elsif (CONDITION2) then
— séquence2 d’opération
Elsif (CONDITION3) then
— séquence3 d’opération
Else
— séquence_opt d’opération
End if ;
Ici, on fait un encodage prioritaire : si la CONDITION1 n’est pas réalisée, on teste la CONDITION2, et si elle n’est pas elle-même réalisé, on testera alors la CONDITION3.
Si les conditions 1 et 3 sont réalisées, par exemple, seule la séquence 1 sera executé, du fait de l’encodage prioritaire
1.2 Case
Case expression is When VALUE_1 =>
— séquence1 d’opération
When VALUE_2 to VALUE_5 =>
— séquence2 d’opération
When VALUE_6 | VALUE_8 =>
— séquence3 d’opération
When others =>
— séquence3 d’opération
End case ;
L’évaluation des conditions doit être impérativement disjointe : par exemple, on ne peut avoir « when VALUE_2 to VALUE_5 » et when « VALUE_5 to VALUE_6 ».
Il faut toujours mettre un « when others ». Cela permet de couvrir tous les cas de figure, et donc de ne pas avoir des états indéterminés.
1.3 For Loop
La variable I ne doit pas être déclarée, elle l’est déjà par la fonction for..loop. C’est une variable locale à la fonction loop.
- Assignation concurrente
2.1 Ecritureéquivalenteaunifhorsprocess
Cette écriture permet de remplacer un process simple qui ne contient qu’une boucle if avec affectation sur un seul signal.
Signal OP,IP : std_logic_vector(3 downto 0) ; (…)
For I in 0 to 3 loop OP(I) <= IP(3 – I) ; End loop ;
Process (A, B, C, T) Begin If (T > 5) then OP <= A ;
Elsif (T < 5) then OP <= B ; Else OP <= C ;
End if ; End process ;
Affectation dans un process Affectation hors process
Romain Berny
OP <= A when T > 5 Else B when T < 5 Else C ;
2.2 Ecritureéquivalenteauncasehorsprocess
Cette écriture permet de remplacer un process simple qui ne contient qu’une boucle if avec affectation sur un seul signal.
Process (A, B, C, T) Begin Case T is When 0 to 4 =>
OP <= B ; When 5 => OP <= C ; When others OP <= A ; End case ; End process ;
Affectation dans un process Affectation hors process
- Variables vs signaux
Dans un process, un signal n’aura sa nouvelle valeur d’affectée qu’à la fin du process. A l’inverse, la variable aura sa nouvelle valeur affectée immédiatement.
C’est pourquoi, a l’intérieur d’un process, on préfèrera utiliser des variables intermédiaires plutôt que des signaux intermédiaires. De plus, l’utilisation de variable intermédiaire soulage l’écriture du process, car étant déclarée à l’intérieur du process, on ne les déclare pas dans la liste de sensitivité.
With T select OP <= B when 0 to 4, C when 5, A when others ;
Signal A, B, P : integer ; Begin Process ( A, B) Variable VM, VN : integer ; Begin
VM := A ; VN := B ; P <= VM + VN ; End process ;
Signal A, B, P : integer ; Signal SM, SN : integer ; Begin Process ( A, B, SM, SN) Begin
SM <= A ; SN <= B ; P <= SM + SN ; End process ;
Cas 1 : utilisation de variables Cas 2 : utilisations de signaux
Dans le cas 1, P est affecté en une itération du process.
Dans le cas 2, P est affecté en deux itérations : une première itération va affecter SM et SN en fin de process, ce qui va re-déclencher l’exécution pour que P reçoive la somme de SM et SN.
- Process Clocké
- ecriture d’un process clocké
Lorsque l’on doit décrire en VHDL une machine d’état synchrone, on utilise un process qui réagit sur front d’horloge. Il existe plusieurs façons d’écrire en VHDL un tel process.
1.1Reset synchrone
Reset synchrone signifie que le reset ne sera pris en compte que lors d’un front d’horloge. Dans la majeure partie des cas, on lui préfèrera un reset asynchrone, décrit au point 1.2. Dans le cas d’un reset synchrone, seule l’horloge est utile dans la liste de sensibilité, car il n y a qu’un front d’horloge qui entraînera une action.
Library IEEE ; Use IEEE.std_logic_1164.all ; Use IEEE.std_logic_arith.all ;
Entity …
Architecture … Signal clk, rst : std_logic ; Signal count : unsigned(3 downto 0) ; Begin
Process(clk) begin If clk’event and clk=’1’ then
If rst = ‘1’ then Count <= « 0000 » ;
Elsif (count >= 9) then Count <= « 0000 » ; Else Count <= count + 1 ; End if ;
End if ; End process ; End architecture ;
On peut remplacer « if clk’event and clk=’1’ » par « if rising_edge(clk) ».
Néanmoins, il y’a une petite différence entre ces deux écriture. Dans le premier cas, on attend un évenement sur clk, et on vérifie que le nouvel état est ‘1’. Cela veut dire que l’état précédent peut être n’importe quoi (‘X’, ‘U’, ‘Z’, ‘0’, …).
Avec la fonction rising_edge() , la condition ne sera vrai que si il y’a transition de la valeur ‘0’ a la valeur ‘1’.
Il faut garder cela à l’esprit, car c’est ce qui peu faire la différence dans une simulation, même si d’un point de vue matériel, cela sera synthétisé de la même façon.
Enfin, il existe une dernière façon d’écrire un process synchrone, à l’aide la fonction « wait until », qui remplace la boucle « if .. then »
Process(clk) begin Wait until clk’event and clk=’1’ ; If rst = ‘1’ then Count <= « 0000 » ; Elsif (count >= 9) then Count <= « 0000 » ; Else Count <= count + 1 ; End if ; End process ; End architecture ;
L’avantage de cette écriture est d’enlever un niveau de if .. then. D’une manière générale, on cherchera toujours a minimier le nombre de boucle if imbriquées, car plus il y’a de niveau imbriqués, plus cela sera dur à synthétiser.
On peut bien évidement écrire également
Wait until rising_edge(clk) ;
Enfin, il faut noter plusieurs points importants : • Tout ce qui a été précédemment écrit fonctionne pour une détection sur front
descendant. Dans ce cas, on utilisera «clk’event and clk=’0’», ou la fonction « falling_edge() ».
- D’une manière générale, après un « if clk’event », il n’y aura pas de else. Ecrire la chose suivante est une aberration, et renverra une erreur de compilation.
La mémorisation est en effet toujours implicite lors de l’utilisation d’un process synchrone.
If clk’event and clk=’1’ then if (count >= 9) then
Count <= « 0000 » ; Else Count <= count + 1 ; End if ;
Else count <= count ;
End if ;
- Sauf exception, une même bascule ne peut être active sur front montant et descendant en même temps. C’est pourquoi il n’est pas correct d’écrire les choses suivantes :
If (ou wait until) clk’event if (ou wait until) clk’event and (clk = ‘0’ or clk = ‘1’)
if clk’event and clk = ‘1’ then … elsif clk’event and clk = ‘0’ then …
end if ;
Ces différentes écritures renverront toujours une erreur de compilation dans un outil de synthèse.
- Il faut également faire attention à bien écrire un process synchrone.
If (clk = ‘1’) then Wait until clk= ‘1’
Ces deux écritures ne donneront pas un process synchrone, même si la syntaxe avec le « wait until » parait juste.
1.2 Reset asynchrone
Dans la majeure partie des cas, on préfèrera utiliser un process asynchrone. Dans ce cas là, le signal de reset doit impérativement se trouver également dans la liste de sensibilité.
Voici ce que cela donnera en reprenant les exemples précédents :
Library IEEE ; Use IEEE.std_logic_1164.all ; Use IEEE.std_logic_arith.all ;
Entity …
Architecture … Signal clk, rst : std_logic ; Signal count : unsigned(3 downto 0) ; Begin
Process(clk, rst) Begin If rst = ‘1’ then Count <= « 0000 » ; Elsif (clk’event and clk=’1’) then
if (count >= 9) then Count <= « 0000 » ; Else Count <= count + 1 ; End if ;
End if ; End process ; End architecture ;
Pour éviter les deux boucles if imbriquées, on pourra en plus remplacer la seconde boucle par un case sur le signal count.
Process(clk, rst) Begin If rst = ‘1’ then Count <= « 0000 » ; Else Wait until rising_edge(clk) ;
if (count >= 9) then Count <= « 0000 » ; Else Count <= count + 1 ; End if ;
End if ; End process ; End architecture ;
Ici, on reprend l’écriture du « wait until ». On voit que le reset asynchrone impose de toute façon d’utiliser une boucle if.
à lire:
elen040_transpvhdl
exemples de programmation
exemples-vhdl
Partager :
- X
Tag » Apprendre Vhdl
-
Introduction Au Langage VHDL - Thibaut Cuvelier
-
[PDF] Cours Initiation VHDL - LAAS-CNRS
-
Cours De VHDL #1. Introduction Et Structure D'un Programme
-
Meilleurs Cours De Langage VHDL En Ligne - Mise à Jour - Udemy
-
[PDF] Vhdl.pdf
-
[PDF] Introduction à VHDL
-
Apprentissage VHDL - Forum Futura Sciences
-
[PDF] Le Langage VHDL - EPFL
-
VHDL Pour Les Débutants
-
La Meilleure Façon D'apprendre VHDL? - AskCodez
-
2.5 Introduction Au VHDL - SEMAINE 2 | Coursera
-
[PDF] Le Language VHDL
-
[PDF] Introduction à La Conception Numérique En VHDL
-
[PDF] Le Langage VHDL - Dunod