Qu'est ce que le langage VHDL ? Comment programmer dasn ce langage ? Introduction 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 :Commencer obligatoirement par une lettre,Peuvent contenir des lettres, chiffre et -. Sommaire masquer 1 Qu'est ce que le langage VHDL ? Comment programmer dasn ce langage ? 1.1 Introduction 1.2 Découverte du langage 1.2.1 A) Les librairies 1.2.2 B) Déclaration de l’entité 1.2.3 C) Architecture interne 1.3 Les Signaux 1.3.1 A) Les bits 1.3.2 B) Booléen 1.3.3 C) Les entiers 1.3.4 E) STD_LOGIC 1.3.5 F) STD_LOGIC_VECTOR 1.3.6 Les nombres signés ou non signés 1.3.7 Un signal n'est pas une variable ! 1.4 Flots de données 1.4.1 A) Affectation simple 1.4.2 B) Affectation conditionnel : when … else … 1.4.3 C) Affectation sélective : structure with…select 1.5 Comportemental 1.5.1 A) Description séquentielle : Process 1.5.2 Comment réveiller un process ? 1.5.3 B) Structure : if…else…end if; 1.5.4 C) Structure if … then … elsif … then … else … end if; 1.5.5 D) Structure case 1.6 Structurelle 1.6.1 A) Instanciation composant Découverte du langage Le plus simple pour comprendre comment fonctionne le VDHL est de voir un exemple et de l’expliquer : ------------------------------------------------------------- -- Liste des librairies et objets importés ------------------------------------------------------------- library IEEE; use IEEE. STD_LOGIC_1166.ALL; --------------------------------------------------------------- --Déclaration de l'entité -------------------------------------------------------------- entity porte_OU is port ( entree1 : in std_LOGIC; entree2 : in std_LOGIC; sortie : out std_logic); end porte_OU; ------------------------------------------------------------- --Architecture interne ------------------------------------------------------------- architecture arch of porte_OU is begin sortie <= entree1 OR entree2; -- on décrit la porte ou end arch; 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 :A) Les librairiesLa 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.La librairie IEEE : C’est une librairie indispensable pour contrôler des composants en VDHL.STD_LOGIC_1164 : Permet d’utiliser les composants de la librairie IEEE. Use est un mot qui permet d’utiliser la librairie et All permet d’importer toute la librairie.B) Déclaration de l’entité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.Port (A,B : in std_logic); : signaux caractérisées d’un mode et d’un type. Il y a 4 modes possibles ( in pour les entrées, out pour les sorties, inout pour les signaux bidirectionnel et le buffer pour les signaux de sortie réentrants). Il y a deux types de base pour les ports : scalaire : std_logic et vectoriel : std_logic_vector.C) Architecture interneL’architecture interne est le coeur du programme. C’est ici que l’on écrire la correpondance entre les entrées et les sorties.Architecture arch of porte_OU is : c’est l’architecture interne, on l’écrit architecture nom de l’architecture, of nom de l’entité isSignal entrees : std_logic_vector(1 to 2); : C’est les signaux internes entre l’architecture et begin. On écrit les signaux nomDuSignal : typedeSignal := valeurInitiale Les Signaux 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). A) Les bitsLes bits peuvent prendre des valeurs 0 ou 1. Ils sont peu utilisés car insuffisant pour la simulation. B) BooléenPeut prendre la valeur vrai ou faux. Type de référence pour les structures conditionnelles. C) Les entiersLes 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 : E) STD_LOGICSTD_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 : ValeurDé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 F) STD_LOGIC_VECTORSTD_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 :STD_LOGIC_VECTOR (3 downto 0) := (other => ”0”) Veut dire qu’il y aura un tableau de quatre 0.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. Les nombres signés ou non signés Il existe deux types de nombre : Les nombres signés et non signés.Les nombres non signés (unsigned) : Ceux qui sont seulement entier positif. Une variable sur 8 bits peut prendre des valeurs entre 0 et 255.Les nombres signés (signed) : Elle peut prendre des valeurs négatives et positives entre -127 et 128.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. Un signal n'est pas une variable ! 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. Flots de données 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 :Affectation simpleAffectation conditionnel : when … else …Affectation sélectives : with…select…A) Affectation simpleOn 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” : Entree1Entree2Sortie1000011101110 Entree1Entree2Sortie2000010100111 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 : -- Demi Additionneur --Librairie library ieee; use iee.std_logic_1164.all; --Entité entity Demi_additionneur is port( Entree1 : in std_logic; Entree2 : in std_logic; Sortie1 : out std_logic; Sortie2 : out std_logic); end Demi_additionneur --Architecture architecture flots_de_donnee of Demi_additionneur is begin Sortie1<= Entree1 XOR Entree2; Sortie2 <= Entree1 AND Entree2; end flots_de_donnee B) Affectation conditionnel : when … else …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” : Entree1Entree2Sortie1000011101110 Entree1Entree2Sortie2000010100111 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 : --Demi Additionneur --Librairie library ieee; use ieee.std_logic_1164.all; --Entité entity demi_additionneur is port ( Entree1 : in std_LOGIC; Entree2 : in std_LOGIC; Sortie1 : out std_LOGIC; Sortie2: out std_LOGIC); end demi_additionneur; --Architecture architecture flots_de_donnee of demi_additionneur is begin Sortie1 <= '1' when ((entree1 ='0' AND entree2='1') OR (entree1 ='1' AND entree2='0')) else '0'; Sortie2 <= '1' when (entree1 ='1' AND entree2='1') else '0'; end flots_de_donnee; C) Affectation sélective : structure with…selectOn 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 combiniaisonsnon 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 : --Demi Additionneur --Librairie library ieee; use ieee.std_logic_1164.all; --Entité entity demi_additionneur is port ( Entree1 : in std_LOGIC; Entree2 : in std_LOGIC; Sortie1 : out std_LOGIC; Sortie2: out std_LOGIC); end demi_additionneur; --Architecture architecture flots_de_donnee of demi_additionneur is signal entrees : std_LOGIC_vector(1 to 2); begin entrees <= Entree1 & Entree2; Sortie1 <= '1' when "01" or "10", '0' when others; Sortie2 <= '1' when "11", '0' when others; end flots_de_donnee; Comportemental 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 :Description séquentielle : ProcessStructure : if…else…end if;Structure : if…then…elsif…then…else…end if;Structure : case… A) Description séquentielle : ProcessUn 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. Comment réveiller un process ? 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é. nom_du_process : process(liste_de_sensibilite) --Déclaration des variables interne au process -- Déclaration des constantes begin -- Description séquentielle -- de code les uns à la suite des autres end process nom_de_process; B) Structure : if…else…end if;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 : if condition then action_du_then; else action_du_else; end if; Sur plusieurs lignes : if condition then -- actions du then end if; if condition then -- actions du then else -- actions du else end if; C) Structure if … then … elsif … then … else … end if;Sur une seule ligne : if condition then action_du_then elsif action_du_elsif then action_du_then else action_du_else end if; Sur plusieurs lignes : if condition then -- actions du then elsif condition_e then -- actions du elsif then else -- actions du elsif else end if; Cette structure est assez similaire à celle du when then. D) Structure case Série de contrôles parallèles visant à vérifier une condition. case signal_variable is when val => -- actions du cas signal_variable = val when vald to valf => -- actions du cas signal_variable compris entre vald et valf when val1|val2|valn => -- actions du cas signal_variable = val1 ou val2 ou valn when others => -- actions pour tous les autres cas end case; 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 : Entree1Entree2CinSortieCout0000000110010100110110010101011100111111 --------Additionneur------- --Librairie library ieee; use ieee.std_logic_1164.all; --Entité entity Additionneur is port ( Entree1 : in std_logic; Entree2 : in std_logic; Cin : in std_logic; Sortie : out std_logic; Cout : out std_logic; end additonneur; --architecture architecture description_comportemental of additonneur is signal Entrees : std_logic_vector ( 2 downto 0); signal Sorties : std_logic_vector (1 downto 0); begin Entrees <= Entree1 & Entree2 & Cin; -- On concaténate les entrées Cout <= Sorties(1); Sortie <= Sorties(0); additonneur_process : process(Entrees) begin case Entrees is when "000" => Sorties <= "00"; when "001" => Sorties <= "01"; when "010" => Sorties <= "01"; when "011" => Sorties <= "10"; when "100" => Sorties <= "01"; when "101" => Sorties <= "10"; when "110" => Sorties <= "10"; when "111" => Sorties <= "11"; when others => Sorties <=,(others => 'x'); end case; end process additonneur_process; end description_comportemental Structurelle 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) ;A) Instanciation composantOn 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_composantPort 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 );