FPGA CPLD : Mise en oeuvre du CPLD : Tutoriel VHDL 1

De Wiki_du_Réseau_des_Electroniciens_du_CNRS
Aller à la navigationAller à la recherche

Ce tutoriel a pour but de vous présenter l'environnement de développement et des premiers exemples de codes VHDL.

Retrouvez l'ensemble des fichiers source sur le SVN<ref>Retrouvez toute l'aide pour vous connecter à la page suivante : Tutorial d'utilisation de TortoiseSVN </ref>, répertoire /Groupe_FPGA_CPLD/Tutoriel/


Découverte de l'environnement ISE

Si vous ne l'avez déjà fait, rendez-vous sur la page Installation de l'environnement de développement pour y trouver les indications sur l'installation et la gestion de la licence pour ISE.

Sinon lancez "ISE Project Navigator" depuis le menu démarrer en choisissant entre 32 ou 64 bits en fonction de votre PC :


Au démarrage de l'application, vous aurez une fenêtre qui ressemble à ceci :

Fenêtre principale d'ISE

Vous avez deux panneaux principaux dans la fenêtre de l'application. A gauche, le panneau ne contient pour l'instant que l'onglet "Start", d'autres s'ajouteront lorsque vous aurez créé un nouveau projet. En bas se trouve la "Console". Ici par contre vous trouvez déjà plusieurs onglets où vous trouverez rapidement un résumé des erreurs et warnings lors de la compilation.

Dans la barre d'outils en haut, vous trouverez les outils classiques d'édition de texte, de recherche et d'organisation des fenêtres. La partie avec les loupes ne sert que lorsqu'on utilise un schematic. Il y a aussi les boutons d'accès à l'aide, les raccourcis pour la compilation et les rapports. Le dernier bouton à droite de la barre d'outils affiche le menu des Language Templates (nous y viendrons par la suite). Il n'y a pas la possibilité de personnaliser votre barre d'outils comme dans la plupart des logiciels.


Mon premier code VHDL

Les premiers codes que nous allons écrire consistent à allumer une LED de différentes façons.

Retrouvez les sources de cet exemple à l'adresse suivante sur le SVN : websvn:/Groupe_FPGA_CPLD/Tutoriel/RdE_Pulse_Generator_Tuto1

Nous allons créer notre premier projet, pour cela:

  • Cliquez sur "File > New Project..."
  • Donnez un nom à votre projet, comme par exemple "RdE_Pulse_Generator_Tuto1"
  • Choisissez le répertoire de travail
  • Dans la liste déroulante "Top-level source type" choisissez HDL.
  • Cliquez sur "Next" et renseignez ensuite les champs comme suit :
ISE - Project settings
ISE - Project settings

→ Vérifiez les informations et cliquez sur "Finish".


Dans le panneau de gauche de nouveaux onglets sont apparus. L'onglet "Design" vous montre l'architecture de votre design. Tout en haut, vous pouvez voir deux puces "Implementation" et "Simulation". Cela vous permet de passer facilement avec le même design à l'architecture pour l'implémentation dans le composant et les architectures pour la simulation qui seront différentes.

L'onglet "Files" recense l'ensemble des fichiers que vous avez créé du projet. Vous pouvez ajouter des fichiers qui ne font pas partie de l'architecture, ils apparaîtront dans cet onglet. Le dernier onglet présente les fichiers sous forme de librairies.


Nous allons ajouter un fichier VHDL à notre design. Ne cliquez pas sur le bouton "New" (équivalent à "File > New") de la barre d'outil, cela vous créerait un fichier vide qui ne serais même pas ajouté automatiquement au projet.

  • Revenez à l'onglet "Design" et faîtes bouton droit, "New Source..." (équivalent à "Project > New Source...").
  • Sélectionnez "VHDL Module" dans la liste et donnez un nom à votre fichier (ajoutez "..._Top" à la fin de votre fichier n'est pas obligatoire mais permet de bien désigner les différents fichiers et les différencier des fichiers qui seront créés lors de la compilation qui porteront le même nom que le nom du projet).
ISE - New Source
ISE - New Source
La case à cocher "Add to project" est cochée par défaut et c'est ce que nous souhaitons, laissez la ainsi.
  • Cliquez sur "Next"
  • Complétez "Port Name" et "Direction" pour notre tutoriel avec "BTN0" et "LD0"
ISE - Define module
ISE - Define module

Vous pouvez voir que nous pouvons renseigner certaines informations. Il n'y a aucune obligation à remplir ces champs (parfois ça ne fait pas gagner de temps). Renseignez les champs comme indiqué et cliquez sur "Next". Vérifiez le résumé et cliquez sur "Finish".

Voici le résultat que vous devez obtenir :

----------------------------------------------------------------------------------
-- Company: 
-- Engineer: 
-- 
-- Create Date:    13:52:04 02/06/2014 
-- Design Name: 
-- Module Name:    Rde_Pulse_Generator_Tuto1_Top - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity Rde_Pulse_Generator_Tuto1_Top is
    Port ( BTN0 : in   STD_LOGIC;
           LD0  : out  STD_LOGIC);
end Rde_Pulse_Generator_Tuto1_Top;

architecture Behavioral of Rde_Pulse_Generator_Tuto1_Top is

begin

end Behavioral;

Le fichier a été créé avec un squelette basique, mais déjà tout à fait synthétisable et implémentable. Malheureusement il ne fera rien !
(Remarque: la date est au format des États-Unis, soit Mois/Jour/Année)


Passons déjà en revue ce fichier. On peut constater la façon dont sont faits les commentaires, à l’aide de deux tirets "--". Il n’est pas possible en VHDL de faire des commentaires par bloc. L’outil par contre vous permet de commenter plusieurs lignes en même temps avec le raccourci (Alt+C) ou dans le menu contextuel "Comment/Lines". On décommente de la même façon avec (Alt+Shift+C) ou dans le menu contextuel "Uncomment/Lines". Faites un essai en sélectionnant des lignes au hasard. Le commentaire commence après les deux tirets, il est donc possible de commencer un commentaire dans le prolongement d’une ligne de code.

Le fichier se divise en plusieurs parties :

  • Les appels aux librairies (lignes 20 à 30)
  • L’entité (lignes 32 à 35)
  • L’architecture (lignes 37 à 41)

L’entité et l’architecture sont indissociables et doivent être décrit dans le même fichier.

La librairie "IEEE.STD_LOGIC_1164" est indispensable puisqu’y est référencé l’ensemble de types de signaux de base, des fonctions de base, comme les STD_LOGIC, les STD_LOGIC_VECTOR, les fonctions OR, AND, etc. Nous rentrerons un peu plus dans le détail plus tard. Pour l’instant contentons-nous de cette seule librairie.

L’entité est la vue que l’on a du module vu de l’extérieur, considérant l’intérieur comme une boîte noir. On ne décrit que les ports d’entrée/sortie du module. On retrouve d’ailleurs les ports que nous avons renseignés lors de la création du fichier. Il est possible d’ajouter à l’entité des "Generic" qui sont des paramètres autres que les ports, comme des constantes. Nous verrons cela par la suite.

L’architecture se divise en deux parties, avant et après le terme "begin". Avant le "begin", on procède à la déclaration des signaux internes à l’architecture de l’entité, des constantes, des types, etc. Après le "begin", on décrit le fonctionnement de l’architecture.

Il est tout à fait possible de décrire plusieurs architectures pour une même entité, en leur donnant des noms différents. Le nom que l’on retrouve le plus souvent est "Behavioral" (littéralement "comportement"), c’est d’ailleurs le nom que ISE a mis par défaut.

Il est aussi possible d’utiliser le même fichier pour décrire plusieurs entités. Sauf si on est sûr de vouloir le faire, il est recommandé de faire un fichier par entité.


Écrivons notre première ligne de code à présent. Après "begin" (ligne 39), sautez une ligne et ajoutez la ligne suivante :

LD0 <= BTN0;

La flèche désigne une affectation. Nous avons donc simplement indiqué que le signal de sortie LD0 devait prendre la valeur de BTN0. Etant donné le câblage de la carte, cela signifie que l’appui sur le bouton 0 doit allumer la LED 0. On rappelle que le VHDL est un langage de description, donc inutile de faire de boucle while() ou quoi que ce soit d’autre. Cette seule ligne permet d’indiquer que la LED est connectée en permanence au bouton.


Regardons à nouveau le panneau de gauche de l’application, onglet "Design". Selon le fichier qui est sélectionné, les options qui sont affichées en dessous sont différentes. Remarquez aussi que notre fichier a un icône avec trois petits carrés dont un vert. Cela indique que cette entité est le "Top Module", le fichier le plus haut dans l'architecture de notre design.

Sélectionnez l'entité "Rde_Pulse_Generator_tuto1_Top" et dans les options en dessous développez la liste associée à "Implement Design" :

Nous pouvons y voir les différentes étapes de la compilation :

  • La synthèse (Synthesize) : transforme le code en une netlist RTL (Register Transfer Level), c’est-à-dire traduit votre code pour le représenter avec des portes logiques et des bascules.
  • La traduction (Translate) : transforme les netlists et les contraintes pour qu'ils utilisent les blocs élémentaires propres au CPLD.
  • Le placement (Fit) : attribue à chaque bloc élémentaire un emplacement dans le composant.
  • La génération du fichier de programmation (Generate Programming File) : génère le fichier à télécharger dans le composant.

Si vous développez ensuite "Synthetize – XST" vous verrez quelques options supplémentaires. "Check Syntax" permet de vérifier la syntaxe du fichier sans faire la synthèse, ce qui permet généralement de résoudre bon nombre d'erreurs. Enlevez le point-virgule à la fin de la ligne que vous avez écrite, sauvegardez et lancez "Check Syntax" en double-cliquant sur l'option. Dans la console en bas de la fenêtre, vous verrez le compte rendu de la vérification. Cliquez sur l'onglet "Errors" et vous aurez le récapitulatif des erreurs détectées. Cliquez sur le lien hypertexte pour que votre curseur se place sur la ligne contenant l'erreur. En l'occurrence, il doit vous indiquer qu'il a trouvé le mot clé "end" alors qu'il y aurait dû trouver un point-virgule avant. Réparez votre erreur et relancez "Check Syntax". Il doit vous indiquez le succès de la vérification. Il peut être important de relancer un "Check Syntax" après avoir apporté une correction car certaines erreurs peuvent en cacher d'autres.

Lancez ensuite la synthèse et amusez-vous avec "View RTL Schematic" et "View Technology Schematic". Ce sera peut-être la seule fois que vous les utiliserez! Vous pouvez descendre dans l'architecture en double-cliquant sur une entité. La vue RTL ne contient rien car notre design est plutôt vide de ce côté-là; la vue technologique ne montre pas beaucoup plus si ce n'est deux buffers :

Il est tout à fait possible de lancer les étapes de compilation suivante mais cela ne servira à rien pour le moment, il manque à notre design le fichier de contraintes pour pouvoir être utilisé dans le composant.

Le fichier de contraintes

Le fichier de contraintes sert à plusieurs choses. Il est indispensable dès que vous écrivez un programme pour une cible. Si vous ne faîtes que simuler ou n'écrire qu'une IP, il n'avez pas à l'utiliser.

En premier lieu, le fichier de contraintes sert à définir le câblage du composant. Dans notre cas, nous allons avoir besoin de câbler les deux signaux LD0 et BTN0 qui sont les ports d'entrée/sortie utilisés par notre code.

Le fichier de contraintes sert ensuite à ajouter des contraintes sur d'autres signaux ou entités. Nous ne les passeront pas toutes en revue car certaines sont compliquées et rarement utilisées.

Certaines contraintes peuvent être indiquées dans le code à l'aide d'attributs. Pour que le code soit portable, il est généralement déconseillé d'écrire certaines contraintes dans le code directement (comme les contraintes de placement des entrées/sorties par exemple). D'autres contraintes sont prises en charge par le synthétiseur. Précisez des contraintes autres permet d'outrepasser les options du synthétiseur.

Xilinx ISE nous permet d'utiliser un outil pour configurer notre fichier de contrainte. Sélectionnez votre fichier "Top" et développez "User Constraints" dans le volet de gauche. Double-cliquez sur "Floorplan IO - Pre-Synthesis". On vous propose de créer un fichier de contrainte, cliquez sur "Yes". La fenêtre qui s'ouvre alors doit être celle-ci :

ISE - Floorplan
ISE - Floorplan

Vous pouvez voir trois sous-fenêtre nommées "Design Browser", "Design Object List - I/O Pins", "Package Pins for xc2c456-TQ144-7" et une fenêtre flottante "Package Pin Legend".

Position physique des entrées/sorties

Dans le "Design Browser", vous n'avez pour le moment que les pins d'entrée/sortie, que vous retrouvez aussi dans la liste des objets en dessous. Vous allez avoir besoin de la position physique des signaux sur la carte de développement. Ces informations se trouvent soit dans le guide utilisateur ou alors sur le schématique.

On voit donc que le bouton BTN0 se trouve sur la pin 143 et que LD0 se trouve sur la pin 69. Dans la partie "Design Object List - I/O Pins", renseignez dans la colonne "Loc" les numéros des pins correspondant. Vous pouvez voir que certaines colonnes se remplissent alors automatiquement avec les informations en adéquation avec la position indiquée. Certaines contraintes ne servent qu'aux calculs de timing dans le design. Elles n'influenceront pas la façon dont est implémenté le design. Par contre, d'autres colonnes peuvent nous intéresser, ce sont les colonnes "Terminaison", "Schmitt" et "Globals". Il y a déjà une résistance de tirage sur le bouton donc inutile de placer une autre résistance sur notre entrée BTN0. En revanche, il peut être intéressant de placer un trigger de Schmitt pour limiter les effets de rebond. Dans la colonne "Schmitt" de la ligne BTN0, tapez ON ou déroulez la liste déroulante et sélectionnez ON. La colonne "Globals" nous servira lorsque nous aurons placer une horloge dans notre design.

Vous devez obtenir la configuration comme suit :

ISE - Floorplan configuré


Enregistrez le fichier (il ne vous demandera pas d'enregistrer avant de fermer!), en choisissant l'option par défaut lors de la confirmation.

Vous pouvez ensuite ouvrir le fichier de contraintes en le sélectionnant dans l'architecture du projet et en sélectionnant "Edit Constraints (Text)" dans les options en dessous.

#PACE: Start of Constraints generated by PACE

#PACE: Start of PACE I/O Pin Assignments
NET "BTN0"  LOC = "P143" | SCHMITT_TRIGGER ; 
NET "LD0"   LOC = "P69" ; 

#PACE: Start of PACE Area Constraints

#PACE: Start of PACE Prohibit Constraints

#PACE: End of Constraints generated by PACE

Les modifications dans le fichier de contraintes sous forme texte seront prises en compte dans l'outil "Floorplan". Cela permet de faire des copier/coller de fichiers de contraintes que l'on peut avoir avec certaines cartes de développement<ref>Par exemple, le fichier de contraintes pour la carte Nexys3 : [1].</ref>.

La compilation et les rapports

Notre design est basique mais désormais complet et implémentable. Nous allons parcourir les options de compilation. Elles n'auront pas d'influence sur nos designs actuels mais vous pourrez être amené à les utiliser dans vos futurs projets.

Après avoir sélectionné le fichier "Top", faites bouton droit sur "Implement Design" et sélectionnez "Process Properties". Vous ouvrez alors une fenêtre comme suit :

ISE - Process Properties
ISE - Process Properties

Tout d'abord, vous pouvez voir en bas de la fenêtre deux options. La liste déroulante permet de basculer entre la liste des options "normale" et la liste "avancée". La case à cocher permet d'afficher ou non les noms des options sous leur forme "ligne de commande", autant dire que vous pouvez la décocher pour plus de visibilité. Basculez en mode "avancé" pour parcourir l'ensemble des options.

Si vous cliquez sur le bouton "Help" vous aurez accès facilement au descriptif de chaque option. Dans la partie de gauche, vous avez les différentes catégories des options selon les étapes de la compilation.

Voici quelques options intéressantes :

  • Optimization Goal / Effort : Permet de définir la stratégie d'optimisation entre la vitesse et l'occupation. L'effort d'optimisation est plus nécessaire dans les cas où le design a des contraintes fortes. Cela rallonge le temps de compilation avec des étapes supplémentaires d'optimisation.
  • Generics, Parameters : permet de définir les "generics" pour le fichier "Top". Ces paramètres outrepassent ceux saisis dans le code.
  • FSM Encoding Algorithm : Permet de définir l'encodage des machines d'états. En auto, il utilisera le codage le plus approprié au type de machine d'états détecté.
  • Add I/O Buffers : ajoute automatiquement des buffers aux entrées/sorties. C'est parce que cette option était cochée que vous avez des buffers insérés dans la vue "Technology Schematic".

D'autres options sont présentes en fonction du type de cible. Sur un FPGA, vous aurez par exemple des options concernant la synthèse des mémoires RAM et ROM, l'absorption de registres, l'interprétation des MUX, l'utilisation de blocs DSP, etc.

Comme indiqué précédemment, certaines options peuvent être explicitement indiquées dans le code à l'aide d'attributs sur les signaux et les entités. Cela peut permettre de faire cohabiter différentes options de compilation dans le design, comme par exemple de forcer le codage d'une machine d'état dans un certain style et une autre dans un autre style.

Laissez les options comme vous les avez trouvé et fermez la fenêtre. Lancez ensuite la génération du fichier de programmation en double-cliquant sur "Generate Programming File" dans le panneau de gauche. Les étapes en amont sont automatiquement lancées au préalable. Vous obtenez le même résultat en double-cliquant sur "Implement Design".

A la fin de la compilation, ISE vous ouvre une page HTML avec le compte-rendu de la compilation. Cette page vous donne un aperçu rapide du taux d'occupation du CPLD et un compte-rendu des ressources.

ISE - Rapport HTML
ISE - Rapport HTML

Vous pouvez voir que notre design rentre sans problème dans le CPLD (heureusement !). Vous pouvez parcourir les différentes pages dans le menu de gauche pour avoir des détails sur d'autres points. Vous avez aussi accès au rapport sur le timing via "Timing report" en haut de la page. Cliquez dessus pour voir par exemple le temps de propagation entre l'entrée et la sortie (10ns).

Une fois que vous en avez vu assez, revenez à ISE. Toujours dans le panneau de gauche, ouvrez "Design Summary/Reports".

ISE - Design summary
ISE - Design summary

Vous pouvez là aussi parcourir les différents rapports de la compilation. Certains rapports ne viendront s'ajouter que si vous lancez les bons process dans ISE.

La programmation avec iMPACT ou Adept

En temps normal, la programmation d'un FPGA se fait à l'aide d'un cordon JTAG relié au PC sur le port parallèle ou un adaptateur USB comme indiqué en début de chapitre. Digilent propose une autre solution pour ses cartes de développement avec le logiciel Adept. Vous pouvez alors programmer directement votre carte via USB. C'est le composant Atmel de la carte qui fait la conversion en JTAG. Il existe aussi un plugin pour iMPACT.

Vous pouvez à présent connecter votre carte à votre ordinateur par le moyen de votre choix.

Si vous souhaitez utiliser Adept, je vous invite à suivre la procédure décrite dans le guide utilisateur qui se trouve normalement à un emplacement de ce type : "C:\Program Files (x86)\Digilent\Adept".

Si vous souhaitez utiliser le plugin pour iMPACT, suivez le guide utilisateur décrit dans le fichier zip d'installation du plugin, en particulier pour configurer la connexion.

Sinon, lancez "Configure Target Device", ce qui lancera le logiciel iMPACT.

ISE - iMPACT
ISE - iMPACT

La première étape consiste à configurer la connexion. Pour cela passez par le menu "Output\Cable Auto Connect" ou configurer manuellement via "Output\Cable Setup..."

Une fois configuré, vous devez lancer la procédure d'initialisation de la chaîne. Vous pouvez le faire dans le menu "File\Initialize Chain", ou via le bouton dans la barre d'outil en haut de la fenêtre, ou en double-cliquant sur "Boundary Scan" dans la partie gauche de la fenêtre.

L'enchaînement de la "Daisy Chain" (connexion en série) apparait dans la fenêtre principale avec le CPLD placé dessus. Sélectionnez le CPLD et dans le menu contextuel, sélectionnez "Assign New Configuration File...". Dans la fenêtre de dialogue qui s'ouvre, sélectionnez le fichier ".jed" du projet et validez. Sélectionnez à nouveau le CPLD et dans le menu contextuel, sélectionnez "Program".

Ça y est! Votre composant est programmé!

Mon deuxième code VHDL

Retrouvez les sources de cet exemple à l'adresse suivante sur le SVN : websvn:/Groupe_FPGA_CPLD/Tutoriel/RdE_Pulse_Generator_Tuto2

Nous allons modifier notre projet actuel pour l'améliorer. Pour gagner du temps, utilisez l'outil dans le menu "File\Copy Project..." et remplissez les champs comme indiqué :

ISE - Copy Project
ISE - Copy Project

Les fichiers sources vont garder le même nom mais cela n'a que très peu d'importance.

A présent, nous allons rendre notre design synchrone et utiliser le bouton poussoir comme un bouton ON/OFF. Pour la forme, nous allons aussi utiliser l'autre bouton comme d'un bouton reset.

Petit exercice, commencez par ajouter nos nouveaux signaux à l'entité :

Solution

entity Rde_Pulse_Generator_Tuto1_Top is
    Port( CLK	: in   STD_LOGIC;       -- Horloge
          RST	: in   STD_LOGIC;	-- Remise à zéro
	  BTN0  : in   STD_LOGIC;	-- Bouton 0
          LD0 	: out  STD_LOGIC);	-- LED 0
end Rde_Pulse_Generator_Tuto1_Top;


Passons à l'architecture. Supprimez dans un premier temps notre ancienne ligne de code :

LD0 <= BTN0;

Essayons une première solution (elle ne marchera pas mais cela nous permettra de voir certaines choses). La première chose à mettre en place est un "process" qui se déclenchera sur le front montant de notre horloge.

Pour cela, saisissez le code suivant :

BTN0_process : process(CLK,RST)
begin
    
    if (RST = '0') then
        LD0 <= '1';
		
    elsif (rising_edge(CLK)) then
	if (BTN0 = '0') then
	    LD0 <= not(LD0);
	end if;
    end if;

end process;

Avant de synthétiser, essayons de comprendre le contenu de ce code :

  • Ligne 43 :
    • BTN0_process désigne le nom du process. Il est optionnel mais permet de mieux les identifier lorsque nous en avons beaucoup.
    • (CLK,RST) est la liste de sensibilité de ce process. Cela signifie que ce process ne se déclenche que sur un évènement sur l'un de ces signaux.
  • Entre les lignes 43 et 44, il est possible d'introduire des constantes et des variables qui seront interne au process.
  • Ligne 46 : On trouve la forme conditionnelle classique. On observe l'état du signal de remise à zéro, dans notre cas, correspond à un appui sur le bouton.
  • Ligne 47 : Affectation de la valeur 1 à la sortie LD0 ce qui a pour effet d'éteindre la LED.
  • Ligne 49 : Suite de la forme conditionnelle. Cette fois on observe un front montant de l'horloge. rising_edge(CLK) est une macro qui est reconnue par la grande majorité des compilateurs. Préférez cette forme à l'ancienne que l'on trouve encore beaucoup dans la littérature et sur le net, qui est (CLK'event and CLK = '1').
  • Ligne 50 : Une autre condition, sur l'état du bouton BTN0 cette fois, imbriquée dans la précédente.
  • Ligne 51 : Inversion de la polarité de la sortie LD0 pour basculer entre l'état allumé et éteint.
  • Lignes 52, 53 et 55 : On ferme les boucles et indique la fin du process.

Lançons un "Check Syntax" et observons le résultat. Une erreur nous est retournée concernant la ligne 51 sur l'inversion de la polarité. En effet, avec l'assertion not(LD0) nous essayons de relire l'état de la sortie LD0. Dans l'état actuel des choses ce n'est pas possible, un signal indiqué "out" dans l'entité ne peut être lu. Impossible de définir notre signal LD0 comme type "inout" car il est réservé aux signaux "trois états". /!\ Il existe une ancienne syntaxe que l'on retrouve là aussi mais qui est à proscrire qui consiste à utiliser le type "buffer" à la place du type "out". Qui plus est, un type "buffer" ne peut être utilisé que pour un signal interne ce qui n'est pas le cas de notre signal LD0 qui est câblé sur une pin physique.

Nous allons donc rajouter un signal à notre architecture. Modifier l'ensemble de l'architecture comme suit :

architecture Behavioral of Rde_Pulse_Generator_Tuto1_Top is

signal LD0_tmp : std_logic;

begin

LD0 <= LD0_tmp;

BTN0_process : process(CLK,RST)
begin

    if (RST = '0') then
        LD0_tmp <= '1';
		
    elsif (rising_edge(CLK)) then
        if (BTN0 = '0') then
            LD0_tmp <= not(LD0_tmp);
        end if;
    end if;

end process;

end Behavioral;

Notre nouveau signal sert d'intermédiaire et étant interne à l'architecture, il n'a pas de type "in" ou "out". Il suffit ensuite de câbler la sortie LD0 sur ce signal intermédiaire (de façon asynchrone) à la ligne 45.

Ma première simulation

Il peut être intéressant maintenant de vérifier le comportement de notre design avant de l'implémenter. C'est surtout utile lorsque le design est grand et complexe, car une fois dans le composant il ne nous est plus possible d'observer l'état des signaux internes.

Il est possible de faire des choses extrêmement compliquées pour simuler l'ensemble des cas de figure. On peut pour cela écrire de façon comportementale les changements d'état des entrées dans un fichier VHDL dédié qui instancie notre "Top" module. Il est possible de tester les modules d'un design indépendamment même ceux qui sont profondément enterrés dans l'architecture. Cela a l'avantage de permettre de vérifier notre design morceaux par morceaux et de gagner du temps de traitement lors de la simulation (lorsqu'on simule des designs très compliqués au niveau "Top", cela peut prendre de plusieurs dizaines de minutes à plusieurs heures).

Ajouter un nouveau fichier au projet, de type "VHDL Test Bench" avec un nom proche de l'entité que vous simuler. Par exemple, ajoutez "..._TB" à la fin de voter fichier (RdE_Pulse_Generator_Tuto1_Top_TB si vous avez suivi jusque là!)

ISE vous propose de le lier directement à une entité qui lui semble la plus judicieuse. Validez et regardez le fichier qui vous ait généré.

--------------------------------------------------------------------------------
-- Company: 
-- Engineer:
--
-- Create Date:   15:11:09 02/12/2014
-- Design Name:   
-- Module Name:   D:/Xilinx Projects/RdE_Pulse_Generator_Tuto2/RdE_Pulse_Generator_Tuto1_TB.vhd
-- Project Name:  RdE_Pulse_Generator_Tuto2
-- Target Device:  
-- Tool versions:  
-- Description:   
-- 
-- VHDL Test Bench Created by ISE for module: Rde_Pulse_Generator_Tuto1_Top
-- 
-- Dependencies:
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
-- Notes: 
-- This testbench has been automatically generated using types std_logic and
-- std_logic_vector for the ports of the unit under test.  Xilinx recommends
-- that these types always be used for the top-level I/O of a design in order
-- to guarantee that the testbench will bind correctly to the post-implementation 
-- simulation model.
--------------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
 
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--USE ieee.numeric_std.ALL;
 
ENTITY RdE_Pulse_Generator_Tuto1_Top_TB IS
END RdE_Pulse_Generator_Tuto1_Top_TB;
 
ARCHITECTURE behavior OF RdE_Pulse_Generator_Tuto1_Top_TB IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT Rde_Pulse_Generator_Tuto1_Top
    PORT(
         CLK  : IN  std_logic;
         RST  : IN  std_logic;
         BTN0 : IN  std_logic;
         LD0  : OUT std_logic
        );
    END COMPONENT;
    

   --Inputs
   signal CLK  : std_logic := '0';
   signal RST  : std_logic := '0';
   signal BTN0 : std_logic := '0';

 	--Outputs
   signal LD0 : std_logic;

   -- Clock period definitions
   constant CLK_period : time := 10 ns;
 
BEGIN
 
	-- Instantiate the Unit Under Test (UUT)
   uut: Rde_Pulse_Generator_Tuto1_Top PORT MAP (
          CLK  => CLK,
          RST  => RST,
          BTN0 => BTN0,
          LD0  => LD0
        );

   -- Clock process definitions
   CLK_process :process
   begin
	CLK <= '0';
	wait for CLK_period/2;
	CLK <= '1';
	wait for CLK_period/2;
   end process;
 

   -- Stimulus process
   stim_proc: process
   begin		
      -- hold reset state for 100 ns.
      wait for 100 ns;	

      wait for CLK_period*10;

      -- insert stimulus here 

      wait;
   end process;

END;

Le fichier VHDL généré est tout à fait similaire à un autre. L'entité existe mais est vide de ports, car il n'est nul besoin, elle va générer elle-même le comportement de ses signaux.


Dans ce nouveau fichier, on voit de nouvelles choses :

  • Lignes 42 à 49 : Avant de pouvoir utiliser un autre module dans un design il est nécessaire de le déclarer. A peu de choses près, la déclaration d'un composant est identique à la déclaration de son entité. Vous pouvez regarder la similitude entre les deux.
  • Lignes 53 à 55 : Des signaux classiques mais cette fois ils ont été initialisés à l'aide d'un ajout " := '0' " à la fin de leur déclaration. Cette initialisation ne doit se faire que lorsque l'on est dans un fichier de simulation. Dans un fichier implémentable, cela ne correspond aucunement à un état après la programmation ou après une remise à zéro.
  • Ligne 61 : La déclaration d'une constante avec le type "time". Ce type ne peut être utilisé que dans une simulation et n'a aucune signification autrement.
  • Lignes 66 à 71 : On instancie le composant qui a été déclaré précédemment. Il est possible de déclarer plusieurs fois le même composant, pour cela on leur donne des noms différents. Le nom apparaît en premier, ISE l'a nommé "uut" ce qui signifie Unit Under Test. On procède ensuite au mappage des signaux du composant. Le mappage se fait dans ce sens :
<instance_name> : <component_name>
generic map ( 
   <generic_name> => <value>,
   <other generics>...
) 
port map (
   <port_name> => <signal_name>,
   <other ports>...
);
Cela n'a pas d'importance que les signaux ait le même nom que les ports, le compilateur fait très bien la différence.
  • Lignes 74 à 80 : Un process pour générer le signal d'horloge avec une période de 10ns, défini par la constante précédente.
  • Lignes 84 à 94 : Un process pour générer les autres signaux. Ce process se finit avec un "wait" sans condition qui empêche le process de se finir.

Nous allons commencer avec quelques modifications du fichier :

  • Changez l'état par défaut du signal BTN0 à la ligne 55 en le passant à '1' pour simuler le fait qu'il soit relâcher par défaut.
  • Changez la période de l'horloge pour correspondre à celle de notre carte, i.e. 125ns (8 MHz).

Voici ce que notre fichier de simulation doit faire :

  • Relacher le signal de remise à zéro
  • Faire basculer le signal d'entrée BTN0 à '0' avec une certaine durée
  • Faire basculer le signal d'entrée BTN0 à '1' avec une certaine durée
  • Recommencer deux fois la simulation de cet appui

Remplacez le process de stimuli par celui-ci :

-- Stimulus process
stim_proc: process
variable cpt : natural range 0 to 3 := 0;
begin		
   -- hold reset state for 100 ns.
   wait for 750 ns;

      RST <= '1';

      for cpt in 0 to 2 loop
         wait for 666 ns;
         BTN0 <= '0';
         wait for 999 ns;
         BTN0 <= '1';
      end loop;

   wait;
end process;

Nous avons ajouté une variable à notre process qui va servir de compteur pour compter le nombre de boucles. L'utilisation des variables est à proscrire en temps normal sauf si vous êtes sûr de ce que vous faîtes car elle entraîne souvent de nombreuses erreurs. Dans la simulation, tout est permis! (ou presque) donc profitons-en.

Nous avons ensuite introduit une boucle "for ... loop" avec le basculement de notre signal BTN0.

Changeons les paramètres de la simulation. Pour cela sélectionnez votre fichier dans le panneau de gauche et dans les options en dessous, développez "ISim Simulator", ouvrez le menu contextuel sur "Simulate Behavioral Model" et sélectionnez "Process Properties...". Changez la ligne "Simulation Run Time" avec la valeur "7 us" et fermez.

Lancez ensuite en double-cliquant sur "Simulate Behavioral Model". La fenêtre suivante apparaît :

ISE - ISIM
ISE - ISIM

La première chose à faire est de changer le zoom sur le chronogramme car si vous observez le règle temporelle vous verrez que vous ne voyez qu'une toute petite partie de la fin de la simulation. Pour cela, aller dans le menu "View/Zoom/To Full View". Vous retrouver l'icône dans la barre d'outil.

Vous pouvez voir les différents signaux du fichier de simulation. Dans le premier panneau de gauche, vous pouvez descendre dans l'architecture de votre design pour observer des signaux internes à votre design. Par exemple, sélectionnez "uut" dans la liste et dans la liste des signaux dans le panneau adjacent, vous pouvez voir le signal interne "ld0_tmp". Vous pouvez le sélectionner et le faire glisser avec votre souris vers le chronogramme pour l'y ajouter.

Malheureusement le signal ne se trace pas car il n'a pas été simulé. Pour relancer la simulation utilisez les boutons de la barre d'outil en cliquant sur l'icône "Restart". Changez le temps dans la barre d'outil pour y inscrire 7us et cliquez sur "Run for the time specified on the toolbar" juste à côté. Le bouton "Re-launch" sert à recompiler le code si vous l'avez modifier.

Vous pouvez voir que les signaux "LD0" et "LD0_tmp" sont identiques, ce qui normal étant donné le comportement qui a été décrit. Vous pouvez déplacer le curseur (barre jaune) dans le temps, ce qui vous permet d'observer l'état des signaux à un instant donné. L'état des signaux est alors indiqué dans la colonne "Value". Cliquez sur la tracé d'un signal pour voir que le curseur se place automatiquement sur un front. Si vous laissez le bouton de votre souris appuyé et que vous tirez le curseur, vous pouvez mesurer une durée. dans la barre d'outil deux boutons vous servent à déplacer automatiquement le curseur vers la précédente transition ou la suivante.

Passons aux choses sérieuses et étudions le comportement de notre design. Ce qui nous intéresse est de savoir si la LED change d'état lorsqu'on appuie sur le bouton. Contrairement à ce que l'on attendait, la LED se met à clignoter lorsque l'on appuie sur le bouton. Il a donc falloir comprendre d'où vient le problème et le corriger. Retour à notre code...


Modification du code

Retrouvez les sources de cet exemple à l'adresse suivante sur le SVN : websvn:/Groupe_FPGA_CPLD/Tutoriel/RdE_Pulse_Generator_Tuto3

En relisant le code de notre fichier "Top", on peut voir que le comportement que nous avons décrit est le suivant : alterne l'état de la LED lorsque le bouton est appuyé et non alterne l'état de la LED lorsque le bouton change d'état. Le but de la modification est donc de détecter un changement d'état sur le bouton (passage de appuyé à relâché).

Nous allons nous aider d'un outil qui nous est fournit par Xilinx. Dans la barre d'outils, ouvrez "Language Templates" (icône en forme d'ampoule). Dans la fenêtre qui s'ouvre, développez "VHDL" et parcourez l'arborescence pour voir ce qui nous est proposé.

ISE - Language Templates
ISE - Language Templates

Vous pouvez retrouver dans cette liste la quasi-totalité des syntaxes que l'on peut retrouver en VHDL. Le code qui va nous intéresser pour notre cas se trouve dans l'arborescence à l'emplacement vu ci-dessus.

Nous allons l'incorporer et le modifier pour notre design.

architecture Behavioral of Rde_Pulse_Generator_Tuto1_Top is

signal BTN0_debounce : std_logic;
signal LD0_tmp       : std_logic;
signal Q1, Q2, Q3    : std_logic;

begin

BTN0_debounce_process : process(RST,CLK)
begin

   if (RST = '0') then
      Q1 <= '1';
      Q2 <= '1';
      Q3 <= '1';
		
   elsif (rising_edge(CLK)) then 
      Q1 <= BTN0;
      Q2 <= Q1;
      Q3 <= Q2;
   end if;
	
end process;
 
BTN0_debounce <= Q1 and Q2 and (not Q3);

BTN0_process : process(CLK,RST)
begin

   if (RST = '0') then
      LD0_tmp <= '1';
		
   elsif (rising_edge(CLK)) then
      if (BTN0_debounce = '1') then
         LD0_tmp <= not(LD0_tmp);
      end if;
   end if;

end process;

LD0 <= LD0_tmp;

end Behavioral;

Relancer une simulation avec ce nouveau design et vérifier son bon fonctionnement.

Avant d'aller plus loin, commentez votre code pour que vous puissiez vous y retrouver quand vous y reviendrez plus tard. Un exemple ci-dessous :

Solution

----------------------------------------------------------------------------------
-- Company:        Mines ParisTech
-- Engineer:       Cédric Toussaint
-- 
-- Create Date:    13:52:04 02/06/2014 
-- Design Name:    Rde_Pulse_Generator_Tuto
-- Module Name:    Rde_Pulse_Generator_Tuto1_Top - Behavioral 
-- Project Name:   Rde_Pulse_Generator_Tuto2
-- Target Devices: xc2c256-7TQ144
-- Tool versions:  Xilinx ISE 14.7 (P.20131013)
-- Description:    Tutorial pour le groupe "RdE - Pulse Generator"
--
-- Dependencies:   N/A
--
-- Revision: 
-- Revision 0.01 - File Created
-- Revision 1.00 - Fichier fonctionnel et validé
--
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;


--////////--
-- Entity --
--////////--

entity Rde_Pulse_Generator_Tuto1_Top is
    Port( CLK	: in  STD_LOGIC;	-- Horloge
	  RST	: in  STD_LOGIC;	-- Remise à zéro
	  BTN0  : in  STD_LOGIC;	-- Bouton 0
          LD0 	: out STD_LOGIC);	-- LED 0
end Rde_Pulse_Generator_Tuto1_Top;


--//////////////--
-- Architecture --
--//////////////--

architecture Behavioral of Rde_Pulse_Generator_Tuto1_Top is

signal BTN0_debounce : std_logic;   -- Signal du bouton BTN0 avec anti-rebond
signal LD0_tmp       : std_logic;   -- Signal interne pour le LED 0
signal Q1, Q2, Q3    : std_logic;   -- Signaux pour l'anti-rebond


--///////--
-- Begin --
--///////--

begin


--// Process anti-rebond pour le bouton 0 //--
BTN0_debounce_process : process(RST,CLK)
begin

   if (RST = '0') then
      Q1 <= '1';
      Q2 <= '1';
      Q3 <= '1';
		
   elsif (rising_edge(CLK)) then 
      Q1 <= BTN0;
      Q2 <= Q1;
      Q3 <= Q2;
   end if;
	
end process;

BTN0_debounce <= Q1 and Q2 and (not Q3);   -- Détecte un front descendant sur BTN0


--// Process d'alternance de la LED 0 //--
-- La LED change d'état à chaque relachement du bouton BTN0
BTN0_process : process(CLK,RST)
begin

   if (RST = '0') then     -- RAZ
      LD0_tmp <= '1';
		
   elsif (rising_edge(CLK)) then
      if (BTN0_debounce = '1') then    -- Lorsque le bouton est relâché
         LD0_tmp <= not(LD0_tmp);      -- Alternance de l'état
      end if;
   end if;

end process;

LD0 <= LD0_tmp;      -- Branchement de la sortie

end Behavioral;

Faîtes ensuite la synthèse et modifier le fichier de contrainte pour y ajouter les signaux d'horloge et de reset.

Solution

#PACE: Start of PACE I/O Pin Assignments
NET "RST"   LOC = "P94"  | SCHMITT_TRIGGER ;	#Bouton 1
NET "CLK"   LOC = "P38"  | BUFG = CLK ;		#Horloge
NET "BTN0"  LOC = "P143" | SCHMITT_TRIGGER ; 	#Bouton 0
NET "LD0"   LOC = "P69"  ; 			#LED 0

Voilà, nous y sommes presque! Lancez la compilation et vérifiez qu'il n'y ait pas d'erreurs lors de la compilation. Lisez les rapports qui sont générés. Générez le fichier de programmation.

Il ne vous reste plus qu'à programmer le composant, testez votre design sur la carte et à écrire vos propres programmes!


Les primitives et les IP

Retrouvez les sources de cet exemple à l'adresse suivante sur le SVN : websvn:/Groupe_FPGA_CPLD/Tutoriel/RdE_Pulse_Generator_Tuto4

Nous allons voir un autre aspect de Xilinx à l'aide des primitives. Lorsque nous avons créé notre premier fichier, Xilinx a placé en commentaire une librairie nommée UNISIM. Cette librairie permet d'instancier différents blocs de base matériels du CPLD. Ces primitives n'ont pas besoin d'être déclarées, leur déclaration étant contenu dans la librairie. On trouve l'ensemble des primitives dans le "Language Templates", dans l'arborescence "VHDL/Device Primitive Instanciation/CPLD".

Pour la plupart de ces primitives, il est possible d'écrire les comportements en VHDL, ils seront probablement synthétisés de la même façon. Par exemple, il revient au même d'écrire :

Library UNISIM;
use UNISIM.vcomponents.all;

   -- IOBUFE: Bi-directional Single-ended Buffer
   --         CPLDs
   -- Xilinx HDL Language Template, version 14.7

   IOBUFE_inst : IOBUFE
   port map (
      O  => O,   -- Buffer output
      IO => IO,  -- Buffer inout port (connect directly to top-level port)
      I  => I,   -- Buffer input
      E  => E    -- 3-state enable input 
   );

ou

library ieee;
use ieee.std_logic_1164.all;

entity top_bi_dir is
   port(O  : out std_logic;      -- Buffer output
        IO : inout std_logic;    -- Buffer inout port (connect directly to top-level port)
        I  : in std_logic;       -- Buffer input
        E  : in std_logic);      -- 3-state enable input
end top_bi_dir;

architecture archi of top_bi_dir is
begin

   IO <= I when E = '0' else 'Z';
   O  <= IO;
	
end archi;

Dans certains cas il est préférable d'utiliser les primitives, comme par exemple pour le diviseur d'horloge présent sur le CPLD. En écrivant le comportement en VHDL, vous risquez d'utiliser des blocs logiques au lieu d'utiliser le bloc matériel dédié.

Attention toutefois lorsque vous utilisez les primitives, il se peut que votre code ne puisse pas être porté sur une autre cible si la primitive pour ce composant n'existe pas.

Il existe un autre moyen de créer du code rapidement en utilisant les IP. Avec un CPLD, il n'est pas possible de les utiliser. Pour en avoir un aperçu, créez un projet sur un FPGA (Spartan3 - XC3S400 - PQ208 pour l'exemple) et créez une nouvelle source en sélectionnant le premier item : IP (CORE Generator & Architecture Wizard), que vous allez nommer "ROM".

ISE - IPCore ROM 1
ISE - IPCore ROM 1

Vous pouvez parcourir l'arborescence pour voir les possibilités qui vous sont offertes. Certaines IP ne sont pas disponibles pour ce composant et sont grisées; ou sont payantes (indiqué par un cadena dans la colonne License).

ISE - IPCore ROM 2
ISE - IPCore ROM 2

Sélectionnez comme indiqué ci-dessus l'IP "Block Memory Generator", passez les étapes suivantes. Un utilitaire va se lancer qui va vous permettre de configurer votre IP. Le bouton "Datasheet" vous donne accès rapidement à la documentation de l'IP (vous renvoie sur Internet) où vous trouverez toute l'aide pour comprendre les différents paramètres.

Passez à la page 2 en cliquant sur "Next". Dans la liste déroulante "Memory Type" vous pouvez voir l'ensemble des fonctions qui peuvent être configurées par cette IP. Choisissez "Single Port ROM" et "Minimum Area", puis passez à la page 3.

Configurez la taille du bus de données avec la valeur de votre choix, ainsi que la profondeur (choisissez-en une petite pour l'exemple, entre 4 et 8).

Passez à l'étape suivante et laissez décochée les premières cases. Par contre, vous allez cochez la case "Load Init File". Avant de passez à la suite, nous allons créer notre fichier qui contiendra les valeurs de la ROM.

  • Revenez un instant sur ISE et créez une nouveau fichier (pas avec "New Source" cette fois, mais bien avec "File/New")
  • Dans la documentation technique de l'IP, on trouve la syntaxe à utiliser :
; Sample initialization file for a
; 16-bit wide by 4 deep RAM
memory_initialization_radix = 16;
memory_initialization_vector =
B358,
3FF4,
74A6,
ABCD;
  • Enregistrez ce fichier sous le nom "ROM.coe". /!\ Dans la liste déroulante de la boîte de dialogue, vérifiez bien que le type de fichier est sur "All Files (*.*)", au risque de l'enregistrer sous au autre format!
  • Revenez à l'outil de configuration de l'IP et cliquez sur "Browse".
  • Sélectionnez le fichier que vous venez de créer et validez.
  • Cliquez sur "Show" pour voir le contenu de la ROM.
  • Passez ensuite à la page 5, puis à la page 6 pour voir le résumé.
  • Cliquez ensuite sur "Generate" et attendez que le processus de création ait fini.

Dans l'architecture du projet, vous verrez un fichier "ROM.xco" qui vous permet de modifier le fichier de configuration de l'IP. Toute modification passe par une re-génération de l'IP (modification du fichier "ROM.coe" inclus).

Lorsque l'IP est sélectionnée, dans les options en dessous, vous pouvez voir "View HDL Instantiation Template" qui vous ouvre un fichier VHDL avec des parties de code à copier pour votre design pour instancier ce composant.

Notes

<references/>