Le vdhl est un langage informatique qui permet de programmer des cartes FPGA. Les cartes FPGA sont des cartes électroniques qui sont utilisés dans l’industrie pour vérifier que vos idées ou votre programme réponds bien à ce que vous souhaitez faire.
Le vdhl se programme sur différent logiciel : quartus prime (Intel) et vivado (xilinx) Lattice. Pour simuler le programme vdhl vous pouvez utiliser ModelSim.
Le VHDL fait abstraction de la technologie. Ce que l’on fait sur microchip on peut le faire sur n’importe quelle autre carte.
En VDHL il y a un très grand respect du type. On ne pas mettre un float dans une variable int par exemple. Ceci diffère du langage Arduino qui est un « langage un peu plus souple ». De plus le vhdl n’est pas sensitif à la casse, cela veut dire qu’écrire un petit ou un grand A ne change rien.
En VDHL, les indicateurs (signaux, variables, process, fonctions, entiers, architectures) doivent :
Le plus simple pour comprendre comment fonctionne le VDHL est de voir un exemple et de l’expliquer :
Le code que l’on vient d’afficher peut vous faire peur, mais on va le décortiquer ensemble afin de mieux comprendre son fonctionnement :
La première chose que l’on déclarer, ce sont les librairies. Elles contiennent des fonctions déjà construite qui vont nous être utile dans la suite du programme.
Dans la déclaration de l’entité vous devez déclarer les signaux qui vont vous être utile dans la suite de votre programme. L’entity est l’interface entre le monde extérieur et votre carte FGPA.
L’architecture interne est le coeur du programme. C’est ici que l’on écrire la correpondance entre les entrées et les sorties.
Un signal se déclare de cette façon : signal nomDuSignal : typeDeSignal := valeurInitiale;
Les signaux sont caractérisés par un mode et un type.
Les signaux peuvent déclarés sous forme de différents types : Les bits, booléen, STD_LOGIC et STD_LOGIC_VECTOR.
Le signal peut-être en entrée (in) et en sortie (out).
Les bits peuvent prendre des valeurs 0 ou 1. Ils sont peu utilisés car insuffisant pour la simulation.
Peut prendre la valeur vrai ou faux. Type de référence pour les structures conditionnelles.
Les entiers sont un type prédifinis dans VHDL, vous n’avez donc pas besoin d’appeler la libraire de fonction afin de l’utiliser. On peut déclarer des entiers de -2**31 à 2**31.
Voici un exemple :
STD_LOGIC permet de créer une variable qui peut prendre 3 valeurs : 0,1 ou l’état Z.
Le signal peut prendre deux valeurs : Soit une valeur d’entrée comme ceci en écrivant in :
Les signaux peuvent être aussi déclaré comme sortant :
L’état z dans un signal est une valeur aléatoire qui se trouve entre 0 et 1. C’est très souvent une valeur que l’on veut éviter en électronique car on ne sait pas si c’est 1 ou 0. Néanmoins en VHDL, cet état est reconnu :
Le signal STD_LOGIC peut prender plusieurs valeurs différente que l’on vous a détaille dans un tableau :
| Valeur | Définition |
|---|---|
| ‘U’ | Valeur inconnue, pas initialisé |
| ‘X’ | Valeur inconnue forcé |
| ‘0’ | 0 forcé |
| ‘1’ | 1 forcé |
| ‘Z’ | haute impédance (pas connecté) |
| ‘W’ | Valeur inconnue faible |
| ‘L’ | 0 faible |
| ‘H’ | 1 faible |
STD_LOGIC_VECTOR permet de déclarer un tableau de valeur. Vous pouvez l’implémentez de cette façon :
Il y a deux manière de déclarer un tableau, d’utiliser downto ou or :
En utilisant to, vous devez d’abord mettre la première valeur du tableau en premier et après le to la dernière valeur. En écrivant 0 to 1 le tableau aura une valeur. Cette valeur se déclare après le « = ». Vous pouvez choisir entre la valeur 0 ou 1.
En utilisant downto, c’est l’inverse. On place d’abord le numéro le nombre le plus important au début et le plus petit nombre après le downto. Par exemple écrire 0 downto 3 n’aurait pas été possible.
Ici tableau(3) est le bit de poids fort et tableau(0) est le bit de poids faible.
S vous avez les mêmes valeurs dans un tableau, par exemple faire un tableau de dix zéro ou bien un tableau de trois 1, vous pouvez la fonction other Il y a deux manières de déclarer un tableau :
L’agréga est pratique pour faire un grand tableau du même chiffre mais si vous souhaitez un 1 au milieu des 0 il faudra utiliser la première méthode.
Il existe deux types de nombre : Les nombres signés et non signés.
Avec STD_LOGIC, on ne sait pas si on a un nombre signé ou pas, c’est pour ça que l’on a besoin de la librairie NUMERIC_STD.
Dans un process, on peut trouver des affectations de signaux ou de variables. Contrairement aux variables, l’affectation du signal n’a pas un effet immédiat. On ne peut modifier que la valeur future du signal. Par défaut, c’est la valeur que prendra ce signal au prochain pas de simulation qui est affectée, valeur qui ne deviendra effective qu’après la fin du process.
Définition: on écrit explicitement les fonctions booléennes que l’on veut voir implémentées (à réserver aux plus petits circuits pour des raisons de lisibilité) ;
Domaine Concurrent : Le flot de donnée éxecutent les fonctions entre begin et end en domaine concurrent, cela veut que toute les instructions s’éxécutent en parallèle et il y n’y pas d’ordre. Ceci n’est pas vrai pour les modes comportemental et structurelle.
Le traitements en flots de données fonctionnent avec trois types d’affectations :
On va maintenant voir un exemple pour mieux comprendre comment programmer en VDHL. Le premier exercice sera de faire le programme d’un demi-additionneur :
La première chose à faire est d’identifier les portes logiques du schéma : Celle qui se termine avec la sortie1 s’appelle le XOR ou « ou exclusif » et la porte logique se terminant par Sortie2 est une porte ET (and).
On peut maintenant avoir le tableau logique de ces deux portes :
La porte XOR ou « ou exclusif » :
La porte ET ou « and » :
| Entree1 | Entree2 | Sortie1 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
| Entree1 | Entree2 | Sortie2 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Voici ce que l’on peut en déduire en traduisant les portes logique en code VHDL :
Sortie1 <= Entree1 XOR Entree2;
Sortie2 <= Entree1 AND Entree2;
Voila ce que cela donne avec le programme VHDL au complet :
On va maintenant voir comment faire un programme avec l’affectation conditionnel, c’est à dire avec un when et else.
Voici un exemple de la structure du programme :
Comme on peut voir, nous avons un else ‘0’ à la fin qui fixe la sortie pour la combinaisons non explicité.
On va maintenant reprendre l’exemple du demi-additionneur afin de le programmer avec des when..else.
Pour cela on va reprendre le tableau des valeurs des portes logiques. On va chercher les différents cas pour lequel on obtient 1 en Sortie1 et 1 en Sortie2 :
La porte XOR ou « ou exclusif » :
La porte ET ou « and » :
| Entree1 | Entree2 | Sortie1 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
| Entree1 | Entree2 | Sortie2 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Comme on peut voir, on obtient 1 en Sortie1 si l’entree1 vaut 0 et l’entree2 vaut 1 ou l’entree1 vaut 1 et l’entree2 vaut 0.
On obtient donc ceci :
Sortie1 <= ‘1’ when ((entree1 =’0′ AND entree2=’1′) OR (entree1 =’1′ AND entree2=’0′)) else ‘0’;
Comme on peut voir, on obtient en Sortie2 si l’entree1 et l’entree2 vallent 1.
On obtient donc ceci :
Sortie2 <= ‘1’ when (entree1 =’1′ AND entree2=’1′) else ‘0’;
Voici ce que cela donne avec le programme en entier :
On va maintenant comment faire un programme avec l’affectation sélective et la structure with…select.
Voici un exemple de la structure d’un programme :
La structure se termine toujours par une ligne … when … others; fixant la sortie pour les combiniaisons
non explicitées.
On va maintenant reprendre l’exemple du demi-additionneur afin de le programmer avec des with..select. En reprenant le tableau de l’affectation conditionnel, on obtient ceci :
Défintion : de manière très semblable à un langage de programmation informatique, on précise le fonctionnement voulu à l’aide d’une suite d’instructions de contrôles plus ou moins évoluées (conditions, boucles, etc.), dans un process.
Dans cette partie on va voir :
Un process est une partie du programme ou les instructions sont exécutées séquentiellement (les unes à la suite des autres).
Voici comment on déclare un process : nom_du_process : process(liste_de_sensibilité)
Liste de sensibilité : liste des signaux dont le changement d’état lance l’exécution du process.
Si la de sensibilité est vide on enlève les parenthèses. Dans ce cas, le process est exécuté sans condition.
Un process s’exécute (se reveille) quand un des signaux de sa liste de sensibilité change de valeur.
Une fois arrivé à la fin du process, celui-ci s’endort jusqu’à l’arrivée d’un événement sur un des signaux de sa liste de sensibilité.
La structure conditionnnelle if esle et endi if peut s’écrire en une ligne ou bien sur plusieurs ligne. Le else n’est pas obligatoire.
Sur une ligne :
Sur plusieurs lignes :
Sur une seule ligne :
Sur plusieurs lignes :
Cette structure est assez similaire à celle du when then.
Série de contrôles parallèles visant à vérifier une condition.
C’est similaire à la structure with..select.
On va maintenant voir un exemple de description comportemental avec un additionneur 1 bit :
Comme on peut voir cet additionneur possède plusieurs porte logique : deux portes XOR (ou exclusif), deux porte AND (ET exclusif) et une porte OR (ou exclusif).
Voici le tableau récapitulant les entrées et sorties de l’additionneur :
| Entree1 | Entree2 | Cin | Sortie | Cout |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 |
Défintion: on décrit le circuit comme une série de boîtes noires interconnectées au moyen de signaux (utilisé pour des circuits moyens ou grands) ;
On va voir comment instancier des composants. L’instanciation c’est choisir un composant et l’utiliser comme instance dans la conception de circuits.
Voici comment on déclare une instance : nom_de_l_instance : nom_du_composant
Port map : indique les signaux internes ou les ports qui sont connectés sur les ports du composant.
Voici comment on déclare un port map :
port map (
[ nom_du_port => ] expression,
[ nom_du_port => ] expression );