< Very High Speed Integrated Circuit Hardware Description Language
fin de la boite de navigation du chapitre

Nous avions affirmé dans l’introduction de ce livre que nous laisserions le domaine des 16 bits et des 32 bits à un autre livre. En effet, ce type de processeurs, est utilisé en général avec un système d'exploitation : n'existe-t-il pas un Linux pour le MicroBlaze? Mais nous avons quand même décidé de réaliser un projet autour du MicroBlaze avec nos étudiants d'où l'apparition de ce nouveau chapitre. Nous resterons cependant éloignés des aspects systèmes d'exploitation et des aspects systèmes temps réel.

Remarque

Le MicroBlaze est spécifique à Xilinx ce qui signifie que ce chapitre est peu lisible par les étudiants ou enseignants utilisant Altera par exemple. Il faut savoir quand même qu’il existe une description libre du MicroBlaze chez Opencores, ce qui veut dire qu'on peut l'implanter dans n’importe quel F.P.G.A.. Pour montrer que nous sommes en aucun cas lié avec un constructeur nous espérons réaliser un chapitre sur le N.I.O.S. qui est le concurrent 32 bits de chez Altera. Il est actuellement en cours de rédaction.

Pour les impatients de découvrir le N.I.O.S., reportez-vous aussi au livre de P. P. Chu "Embedded SoPC Design with NIOS II Processor and VHDL Examples".

Remarque

Nous avons classé ce chapitre au niveau 16 même si nous avons l'intention de travailler avec des étudiants de niveau 14. Cela est dû au fait que le projet sera très encadré et l'autonomie des étudiants assez réduite. Il nous faut bien essayer de travailler dans ce contexte avant d’en tirer des conclusions.

La partie logicielle destinée à gérer l’ensemble des opérations nécessaires à la mise en œuvre d'un processeur embarqué MicroBlaze s’appelle l'E.D.K. (Embedded Development Kit). Il s'agit en fait d'une collection de scripts Tcl/Tk liant divers outils en ligne de commande, et d'une interface graphique couvrant l'ensemble.

Terminologie
  • Xilinx "Embedded Development Kit" sera appelé EDK dans la suite de ce chapitre comme dans toute la documentation Xilinx
  • "Xilinx Platform Studio" sera appelé XPS dans la suite de ce chapitre. C'est une des composantes de l'EDK.
  • "Software Development Kit" sera appelé SDK dans la suite de ce chapitre. C'est une des composantes de l'EDK.

L'EDK est donc composé essentiellement de deux logiciels :

  • XPS (Xilinx Platform Studio) destiné à gérer la partie matérielle
  • SDK (Software Development Kit) destiné à mettre à jour la partie logicielle.

Comme ces logiciels sont destinés à en cacher d'autres, ce chapitre sera émaillé de boîtes déroulantes qui elles aussi cacheront ce que Xilinx voudrait cacher avec son environnement de développement. Mais ici, elles peuvent être déroulées pour en apprendre plus.

Réaliser le tutoriel

Xilinx prétend vous faire mettre un MicroBlaze en moins de trente minutes dans un F.P.G.A. et nous pensons sincèrement qu’ils n'ont pas menti à ce sujet. Cela peut laisser croire cependant que les choses sont simples, ce qui est très loin d’être le cas. Dès que vous cherchez à modifier d'une manière ou d'une autre ce tutoriel, vous êtes confrontés à des problèmes. Un exemple peut être ? Si vous avez réalisé votre propre carte F.P.G.A., vous n'avez aucune chance de démarrer le tutoriel : cela ne veut pas dire que c’est impossible, mais cela veut dire que vous prenez le problème dans un sens qui n’est pas prévu par Xilinx comme un point de départ.

Il n’est pas difficile de trouver des tutoriels sur Internet mais en trouver qui correspondent vraiment à la version de votre EDK c’est une autre histoire. Contrairement à ce que l’on trouve sur Internet, nous n'allons pas présenter de copie d'écran qui risquent de devenir obsolètes bien trop vite.

Tutoriel pour carte spartan3

Nous n'allons pas décrire en détail la carte spartan3. Disons qu'elle possède une liaison série, une liaison PS/2, une liaison VGA, 8 interrupteurs, 4 boutons poussoirs, 8 leds, et 4 afficheurs sept segments.

Nous allons décrire brièvement comment faire tourner un MicroBlaze dans une carte spartan 3. Il suffit de suivre le tutoriel pour cela. Vous pourrez suivre plus ou moins facilement notre description sommaire car elle dépend de la version de l'EDK utilisée. Pour information, nous avons utilisé la version 9.2 pour faire ce travail.

Terminologie
  • "Base System Builder" sera appelé BSB dans la suite de ce chapitre.
  • BSB est une composante de XPS Xilinx Platform Sudio

Lancer BSB (Base System Builder)

La création d'un projet se fait avec BSB qui propose une série de boîtes de dialogues parmi les quelles on peut naviguer avec les célèbres "next" et "previous". Nous vous proposons de manière simplifiée la série des valeurs que l’on a pris pour notre projet.

  • Project File Browse
  • I would like to create a new Design
  • board vendor : xilink
  • choisir sa carte : spartan-3 Starter Board
  • Board revision : E
  • next
    • recapitulatif
    • next
      • 50 Mhz 50 Mhz
      • No Debug
      • 16 KB
      • next
        • XPS UART light
        • XPS GPIO pour LEDs_8Bit
        • XPS GPIO pour LEDs_7SEGMENT
        • XPS GPIO pour PUSHBUTTON_3Bit
        • XPS GPIO pour DIP_switch_8Bit
        • XPS MCH EMC pour SRAM
        • next
        • next
          • STDIN : RS232
          • STDOUT : RS232
          • Boot memory : ilmb_cltrl
          • next
            • Memory test : rien changer
            • next
              • Peripheral test : ruen changer
              • next
                • Generate
                • Finish
Remarque

La présentation ci-dessus est difficile à lire : chaque décalage à droite correspond à une nouvelle boîte de dialogue. Elle n'est donnée qu’à titre d'exemple en espérant que le lecteur intéressé par réaliser le tutoriel avec une autre version de l'EDK y trouve suffisamment d'informations pour se lancer et peu importe la façon dont les boîtes de dialogues sont agencées.

Tout est prêt à ce niveau. D'ailleurs BSB se ferme et l’on se retrouve dans XPS. Les fichiers ainsi générés sont :

  • un fichier qui a l'extension .mhs qui sert à décrire le matériel
  • un fichier d'extension .mss qui sert à décrire le logiciel

Il nous faudra apprendre à manipuler ce type de fichier, mais nous ne nous y intéressons pas pour le moment.

Et la suite est laissée à XPS...

Une fois revenu sous XPS faire :

HarwareGenerate Bitsream nous lance une série de commandes qui ont pour objectif de réaliser le fichier .bit.

En fait comme montré dans la boîte déroulante ci-dessous la génération du fichier .bit se fait à l'aide d'un certain nombre de commandes que l’on ne vous demande pas de détailler pour le moment. C'est naturellement pour cela qu'on les a mis dans une boîte déroulante.

Il est temps de faire un petit résumé sur XPS.

À quoi sert XPS ?

On voit que le logiciel XPS est capable de partir d'une spécification générée par une série de boîtes de dialogues et d’en faire un projet complet qui finira donc par le célèbre fichier .bit : il est même possible de lancer le chargement du fichier .bit dans le FPGA. En clair, on peut se passer de l'ISE : plus exactement on n'a pas besoin de le lancer mais en fait il faut l'installer car tous ses outils sont nécessaires.

Remarque

Le morceau de phrase "on peut se passer de l'ISE" dans l'encadré précédent n’est pas exact pour toutes les versions de l'EDK. Il me semble qu'au début la synthèse se faisait avec l'ISE puis que vers la version 9 (nous utilisons la 9.2 pour écrire ce chapitre) on en avait plus besoin et que pour les nouvelles versions on soit revenu en arrière ! Tout ceci nous montre les errements de Xilinx du point de vue de ses logiciels de conceptions. Nous n'avons pas du tout d'expérience avec les logiciels concurrents pour dire si c’est la même chose... Si quelqu’un peut y ajouter son expérience...

Tutoriel pour carte spartan3E

Nous n'allons pas décrire en détail la carte spartan3E. Disons qu'elle possède une liaison série, une liaison PS/2, une liaison VGA, 4 interrupteurs, 4 boutons poussoirs, 8 leds, un afficheur LCD.


Arrivé à ce point nous sommes très satisfaits d’avoir réussi à réaliser le tutoriel. Il nous reste cependant à être capable de modifier ce tutoriel pour l'adapter à nos propres besoins. Deux types de modifications s'offrent à nous :

  • utiliser le matériel existant et réaliser des logiciels qui le mettent en œuvre
  • modifier le matériel existant pour l'adapter à nos besoins.

Comme la première modification est de très très loin la plus simple, nous allons commencer par elle.

Modifier le programme qui s'exécute

Le logiciel responsable de la mise à jour du logiciel s’appelle le SDK. Nous nous sommes longtemps posé la question de l'interaction entre le SDK et XPS, à savoir, nous venons de modifier notre programme comment le faire savoir a XPS ? En fait c’est toute simple :

  1. vous modifiez le programme dans le SDK
  2. une sauvegarde lance sa compilation
  3. on revient dans XPS : "Device Configuration" → Update Bitstream
  4. toujours dans XPS : "Device Configuration" → download Bitstream
Remarque

Si dans l'étape 3 le logiciel vous dit qu’il a rien à faire c’est probablement parce que vous n'avez aucun projet de choisi. Cela se fait en choisissant le projet dans la partie projet où vous pouvez voir plusieurs projets. Dans l'onglet "Applications" choisissez votre projet par un click droit et "Mark to Initialize BRAM".

Une autre manière de faire peut être d’utiliser le SDK seul et voici présenté sous forme de principe la façon de faire :

Développement logiciel avec le SDK seul

Une fois votre partie matérielle réalisée le SDK peut être utilisé seul pour développer la partie logicielle.

  • "Device Configuration" → "Program Hardware Settings..." permet de déterminer quel programme va mettre à jour le fichier .BIT
  • "Device Configuration" → "Program Harware" ou l'icone correspondant permet de charger dans le FPGA.
Fin du principe

Comme d'habitude, cet ensemble d'actions réalisées par des menus cache des commandes plus ou moins complexes que l’on vous a mis dans une boîte déroulante pour les cacher pour l'instant.

Interaction logicielle avec les logicores de base pour la carte spartan3

L'ensemble de ce qui va être réalisé maintenant correspond à des choses assez standard dans le domaine des microcontrôleurs, comme allumer des Diodes électroluminescentes (LEDs), gérer des afficheurs sept segments lire des valeurs sur des interrupteurs... La seule chose à apprendre est comment lire un PORT et comment ces PORTs sont connectés au matériel ?

Lire des interrupteurs

Nos interrupteurs d'entrée sont reliées à un port. Notre problème est donc d'apprendre comment lire le PORT.

//****** C MicroBlaze *******
#include "xio.h" // n’est pas présent par défaut
// ....
Xuint32 data;
//....
data = XIo_In32(XPAR_DIP_SWITCHES_8BIT_BASEADDR);

La fonction qui permet de lire un PORT est donc XIo_In32 et elle demande un nom majuscule comme paramètre. Ce nom est à trouver quelque part. C'est le fichier xparameters.h qui contient ces définitions importantes et il est facile à trouver et à lire avec le SDK. Voici les détails de ce qui nous intéresse :

//****** Extrait du fichier xparameters.h *******

/* Definitions for peripheral DIP_SWITCHES_8BIT */
#define XPAR_DIP_SWITCHES_8BIT_BASEADDR 0x81420000
#define XPAR_DIP_SWITCHES_8BIT_HIGHADDR 0x8142FFFF
#define XPAR_DIP_SWITCHES_8BIT_DEVICE_ID 3
#define XPAR_DIP_SWITCHES_8BIT_INTERRUPT_PRESENT 0
#define XPAR_DIP_SWITCHES_8BIT_IS_DUAL 0

En fait vous n'avez pas besoin de connaître la valeur de l'adresse de base (ici 0x81420000) mais par contre il vous faut connaître comment la désigner.

Allumer des LEDs

Allumer des leds est l'opération inverse de la lecture de valeurs à partir des interrupteurs. Cela veut dire que ce qui était entrée devient maintenant sortie. Sans grande surprise maintenant, la fonction qui s'occupe des sorties s’appelle "XIo_Out32".

//****** C MicroBlaze *******
#include "xio.h" // n’est pas présent par défaut
// ....
Xuint32 data;
//....
XIo_Out32(XPAR_LEDS_8BIT_BASEADDR, data);

Encore une fois cette constante à rallonge est à trouver dans le fichier xparameters.h À noter que les LEDs sont câblées à l’envers : le poids faible de la variable data allumera la led de gauche (qui porte le numéro 7). Voici, encore une fois, comment j’ai trouvé le nom du premier paramètre, en examinant le fichier d'entête :

//****** Extraits du fichier xparameters.h *******
/* Definitions for peripheral LEDS_8BIT */

#define XPAR_LEDS_8BIT_BASEADDR 0x81460000
#define XPAR_LEDS_8BIT_HIGHADDR 0x8146FFFF
#define XPAR_LEDS_8BIT_DEVICE_ID 0
#define XPAR_LEDS_8BIT_INTERRUPT_PRESENT 0
#define XPAR_LEDS_8BIT_IS_DUAL 0

Allumer des afficheurs sept segments

Notre carte est dotée de quatre afficheurs sept segments qu’il nous faut apprendre à utiliser. Bon commençons par chercher l'adresse, toujours avec le fichier d'entête :

//****** Extraits du fichier xparameters.h *******
//* Definitions for peripheral LED_7SEGMENT */

#define XPAR_LED_7SEGMENT_BASEADDR 0x81440000
#define XPAR_LED_7SEGMENT_HIGHADDR 0x8144FFFF
#define XPAR_LED_7SEGMENT_DEVICE_ID 1
#define XPAR_LED_7SEGMENT_INTERRUPT_PRESENT 0
#define XPAR_LED_7SEGMENT_IS_DUAL 0

Mais les choses se compliquent ensuite car vous devez envoyer 8 bits (pour les sept segments et le point) et quatre bits ensuite pour sélectionner un afficheur.

Principe

Quand on part des poids faibles pour monter vers les poids forts, on montre que les 4 bits poids faibles sont pour la sélection de l'afficheur (gauche en premier puis droite). La sélection d'un afficheur se fait par un zéro. Vient ensuite le point, puis les segments gfedcba (dans cet ordre) qui sont actifs à l'état bas.

Fin du principe

Voici donc un bout de code capable d'afficher une valeur hexadécimale sur un afficheur sept segments :

#include "xio.h"
//....
Xuint32 data;
unsigned char t[]={0x01,0x4F,0x12,0x06,0x4C,0x24,0x20,0x0F,0x00,0x04,0x08,0x60,0x31,0x42,0x30,0x38};
//...
// acquisition de data d'une manière ou d'une autre
//....
XIo_Out32(XPAR_LED_7SEGMENT_BASEADDR, t[data&0x000F]<<5|0x1E);

Il est important pour le lecteur d'essayer de comprendre ce code.

Utiliser des logicores moins standard

Lors de la réalisation du tutoriel nous avons choisi d’utiliser un logicore appelé "XPS MCH EMC pour SRAM". Il sert à adresser de la mémoire statique qui est à l'extérieur du FPGA et qui est présente sur la carte spartan-3. Nous allons nous poser la question de son utilisation dans cette section.

Réaliser son propre périphérique et le connecter

C'est là que les problèmes commencent vraiment. Nous allons commencer par un exemple très simple trouvé dans un tutoriel (import_peripheral_tutorial.pdf) qui ne semble pas être un document officiel de Xilinx. Légèrement modifié, il consistera à réaliser notre propre afficheur sur les 8 leds de la carte. C'est un périphérique très simple mais suffisamment complexe pour montrer toutes les facettes du problème.

Présentation générale

Terminologie
  • "Create or Import Peripheral" sera appelé CIP dans la suite ce de chapitre.
  • "Processor Local Bus" sera appelé PLB dans la suite de ce chapitre.
  • "Intellectual-Property Interface" sera appelé IPIF dans la suite de ce chapitre. C'est une librairie hautement paramétrisable et vérifiée donc très conseillée à l'utilisation.
  • "IP Interconnect" sera appelé IPIC dans la suite de ce document. C'est un module VHDL généré automatiquement.

La création d'un nouveau périphérique passe donc par un certain nombre d'étapes que nous allons détailler maintenant.

Étape 1

Pour créer un périphérique il faut lancer CIP. Cela se fait à partir de "Xilinx Platform Studio" dans le menu

  • "Hardware" → "Create or Import Peripheral" (avec la version 9.2)

Cela permet de choisir un certain nombre d'options concernant :

  • la connexion au processeur : par PLB ou autre
  • les services IPIF que l’on utilise
  • "Interrupt Service" : choisir si l’on utilise les interruptions
  • IPIC sera généré automatiquement. Il nécessite l’utilisation de signaux que l’on vous conseillera fortement d'utiliser.

Ces choix permettent de fabriquer un certain nombre de fichiers :

  • un fichier d'extension mdd
  • des fichiers utilisables avec l'ISE

Étape 2

Si cette étape est optionnelle, elle est fortement conseillée. Elle consiste à utiliser l'ISE pour modifier et surtout compiler pour vérification votre périphérique. Bien sûr à ce stade il vous sera très difficile de vérifier votre périphérique car il ne fonctionne correctement, en principe, que connecté au bus du processeur. Pourtant, vous pourrez détecter toutes les fautes de syntaxe que vous êtes susceptibles de faire et c’est déjà pas mal. J'insiste : vous ne pouvez pas faire autre chose que de la vérification de syntaxe à ce stade car les modèles générés automatiquement à l'étape précédente utilisent une librairie inconnue par l'ISE. Nous n'avons pas eu le courage de la rechercher pour l'ajouter au projet pour le moment.

Étape 3

On utilise de nouveau CIP mais en partant dans "Import existing peripheral". Cette phase peut sembler étrange et beaucoup d'entre-vous penseront qu'on pouvait le faire dès l'étape 1. En fait je pense que Xilinx a choisi cette technique de réutiliser CIP, car elle vient après la mise au point de l'étape 2. Son objectif est donc de choisir quel types de fichiers vont être associés au périphérique : vhdl, edn, edf, ngc ou ngo. Il est possible que ce soit à ce moment aussi que les fichiers de programme C soient générés (ou à l'étape 1 ou 4 ???). L'autre différence avec l'étape 1 est qu'ici on dispose des fichiers VHDL alors que l'étape 1 avait comme objectif de les construire.

L'autre point c’est que si l’on suit les étapes comme indiqué dans ce cours, on dispose déjà du modèle de périphérique : il faudra donc choisir "Use Peripheral Analysis Order file (*.pao)" quelque part dans cette étape.

Dernier point : cette étape compile les fichiers VHDL du périphérique.

Étape 4

La connexion du périphérique se fait simplement dans XPS. Il vous faudra utiliser pour cela absolument les trois onglets "Bus Interfaces", "PORT" et "Adresses" pour réaliser ce travail.

Ne pas oublier aussi à ce stade de modifier le fichier ucf.

Mise en œuvre pratique

Nous allons reprendre dans cette section l’ensemble des étapes précédemment décrites pour notre cahier des charges.

Notre cahier des charges

Notre périphérique va être très simple : il consiste à réaliser un PORT de sortie qui soit capable d'allumer les 8 leds de la carte.

Détails sur l'étape 1 : créer le modèle de périphérique

Son objectif est donc de créer le modèle du périphérique que l’on veut réaliser. C'est dans XPS que tout va se passer.

  • Harware → Create or Import Peripheral puis Next
    • cocher "Create template for a new peripheral" puis Next
      • cocher "to an XPS Project" puis Next
        • donner un nom et choisir une version (on choisira comme nom myleds8) puis Next
          • cocher "Processor Local Bus (PLB V4.6)" puis Next
            • laisser coché "user logic software register" puis Next
              • ne rien changer puis Next
                • Choisir un registre (de 32 bits) puis Next
                  • ne rien changer pour les bus puis Next
                    • ne rien changer deux fois puis "Finish"
Remarque

Encore une fois la présentation ci-dessus n’est pas facile à lire mais elle donne les informations principales dont vous aurez besoin quelle que soit la version de l'EDK utilisée. Les décalages à droites représentent des boîtes de dialogues successives.

Détails sur l'étape 2 : vérifier la syntaxe du périphérique

Il nous faut maintenant reprendre ce modèle avec l'ISE, le modifier et vérifier sa syntaxe. L'étape une précédente a généré un fichier de projet pour l'ISE mais avec ma version il n'y a rien dedans. Il faut donc choisir le FPGA cible et ajouter les fichiers à modifier à la main. Puisque notre nouveau périphérique s’appelle "myleds8", deux fichiers VHDL sont générés à l'étape précédente (dans pcore/myleds8_v1_00_a/hdl/vhdl) :

  • user_logic.vhd est un fichier d'interface au bus PLB qui est toujours généré mais qui n’est pas complètement identique à chaque fois. Il dépend en effet du nombre de registres que vous avez choisi dans votre périphérique.
  • myleds8.vhd : son nom dépend de votre nom de périphérique.

Vous avez peut être remarqué qu’à l'étape précédente on ne nous a jamais demandé si l’on avait des sorties dans notre périphérique qui sortiront du FPGA. C'est maintenant que nous allons les ajouter.

Modification du fichier myleds8.vhd

Vous devez chercher dans ce fichier l'entité qui s’appelle myleds8 puis la partie PORT de cette entité et ajouter dans la partie faite pour cela votre sortie qui s’appelle leds8 ici :

port
  (
    -- ADD USER PORTS BELOW THIS LINE ------------------
    --USER ports added here
	 leds8 : out std_logic_vector(7 downto 0);
    -- ADD USER PORTS ABOVE THIS LINE ------------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol ports, do not add to or delete
    SPLB_Clk                       : in  std_logic;
    -- ....

Vous devez connecter cette sortie au bon composant. Il y en a deux et c’est le deuxième qui a une entité "user_logic".

------------------------------------------
  -- instantiate User Logic
  ------------------------------------------
  USER_LOGIC_I : entity myleds8_v1_00_a.user_logic
    generic map
    (
      -- MAP USER GENERICS BELOW THIS LINE ---------------
      --USER generics mapped here
      -- MAP USER GENERICS ABOVE THIS LINE ---------------

      C_SLV_DWIDTH                   => USER_SLV_DWIDTH,
      C_NUM_REG                      => USER_NUM_REG
    )
    port map
    (
      -- MAP USER PORTS BELOW THIS LINE ------------------
      --USER ports mapped here
		leds8 => leds8,
      -- MAP USER PORTS ABOVE THIS LINE ------------------

      Bus2IP_Clk                     => ipif_Bus2IP_Clk,
      Bus2IP_Reset                   => ipif_Bus2IP_Reset,
-- ......

où l’on a ajouté que leds8 est relié à leds8. Mais "user_logic" se trouve dans le deuxième fichier et n'a pas cette sortie pour le moment.

Modification de user_logic.vhd

On commence par ajouter la sortie dans l'entité :

entity user_logic is
  generic
  (
    -- ADD USER GENERICS BELOW THIS LINE ---------------
    --USER generics added here
    -- ADD USER GENERICS ABOVE THIS LINE ---------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol parameters, do not add to or delete
    C_SLV_DWIDTH                   : integer              := 32;
    C_NUM_REG                      : integer              := 1
    -- DO NOT EDIT ABOVE THIS LINE ---------------------
  );
  port
  (
    -- ADD USER PORTS BELOW THIS LINE ------------------
    --USER ports added here
	 leds8 : out std_logic_vector(7 downto 0);
    -- ADD USER PORTS ABOVE THIS LINE ------------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol ports, do not add to or delete
    Bus2IP_Clk                     : in  std_logic;
    Bus2IP_Reset                   : in  std_logic;
    Bus2IP_Data                    : in  std_logic_vector(0 to C_SLV_DWIDTH-1);
    Bus2IP_BE                      : in  std_logic_vector(0 to C_SLV_DWIDTH/8-1);
    Bus2IP_RdCE                    : in  std_logic_vector(0 to C_NUM_REG-1);
    Bus2IP_WrCE                    : in  std_logic_vector(0 to C_NUM_REG-1);
    IP2Bus_Data                    : out std_logic_vector(0 to C_SLV_DWIDTH-1);
    IP2Bus_RdAck                   : out std_logic;
    IP2Bus_WrAck                   : out std_logic;
    IP2Bus_Error                   : out std_logic
    -- DO NOT EDIT ABOVE THIS LINE ---------------------
  );

Maintenant, il va nous falloir relier tout le monde. Ceci est fait en modifiant la fin de l'architecture (regardez de près l'extrait donné ci-dessous, il finit par le "END" de l'architecture) :

------------------------------------------
  -- Example code to drive IP to Bus signals
  ------------------------------------------
  IP2Bus_Data  <= slv_ip2bus_data when slv_read_ack = '1' else
                  (others => '0');

  IP2Bus_WrAck <= slv_write_ack;
  IP2Bus_RdAck <= slv_read_ack;
  IP2Bus_Error <= '0';
  
  -- ceci est ajouté par nos soin :
  leds8 <= slv_reg0(24 to 31);

end IMP;
Remarque

Nous avons perdu du temps pour trouver que nos 8 bits sont reliés aux fils 24:31 puisque nous avons essayé 0:7 auparavant sans succès.

Résumé schématique du travail réalisé

Voici schématiquement ce que nous avons fait :

Travail à réaliser pour une connexion
Conventions de la figure

Pour cette figure les chiffres 1, 2, 3 et 4 désignent l’ordre des opérations présentées ci-dessus (dans les deux sections précédentes) avec leurs quatre extraits en VHDL.

D'autre part les signes + devant des entrées ou sorties veulent dire que l’on a ajouté ces entrées ou sorties dans les entités correspondantes.

Fin du principe

Nous avons utilisé un seul registre de 32 bits : le signal correspondant est généré automatiquement et s’appelle "slv_reg0". Seuls 8 bits de ce signal nous seront utiles.

Détails sur l'étape 3 : enregistrer le périphérique dans l'EDK

Pour entreprendre l'étape 3 qui consiste à enregistrer votre périphérique dans l'EDK, il vous faut des fichiers VHDL correctement écrits car ils seront compilés à ce stade. Cette étape ressemble à l'étape 1 sauf qu'on ne crée plus un modèle de périphérique on l'enregistre.

  • Harware → Create or Import Peripheral puis Next
    • cocher "Import existing peripheral" puis Next
      • cocher "to an XPS Project" puis Next
        • donner un nom et choisir une version (on choisira bien sûr myleds8) puis Next puis "yes" sur boite de dialogue
          • cocher "HDL Source File" puis Next
            • cocher "Use existing peripheral Analysis Order (*.pao)" et chercher votre fichier pao dans le répertoire "data" de votre périphérique puis Next
              • ne rien changer puis Next : c’est cette étape qui compile tout votre périphérique
                • Cocher PLBV46 Slave (SPLB) puis Next
                  • cliquer 6 fois sur Next en décochant l'interruption au passage
                    • puis "Finish"

À ce stade votre périphérique existe bel et bien et peut donc être utilisé dans votre EDK. Il peut être global (utilisable par tous) ou local (utilisable par tous les projets du répertoire courant). Puisque nous avons coché "to an XPS Project", il sera local pour nous.

Détails sur l'étape 4 : connecter le périphérique

Pour connecter votre périphérique, vous le cherchez dans l'onglet "IP Catalog" (à gauche) et "Project Local pcores" et vous double cliquez dessus. Il apparaît alors dans la partie droite de XPS mais il n’est pas encore connecté. Vous allez utiliser les trois onglets pour le faire :

  • onglet "Bus Interface" : au-dessous de myleds8_0 apparaît SPLB en non connecté. Connectez-le en "mb_plb".
  • onglet "Ports" : chercher leds8 et faire "make external" pour les rendre externes au FPGA. Un nom vous sera donné pour vos sorties. Pour nous c’est "myleds8_0_leds8".
  • onglet "Adresses" : choisir 0x84280000 et 1 seule adresse (nous n'avons qu'un seul registre).

Il nous faut maintenant modifier le fichier ucf.

Pour trouver le nom de la sortie véritable à utiliser dans le fichier ucf il vous faudra lire le fichier *.mhs correspondant à votre projet. Pour nous, nous trouvons l'information cherchée au début du fichier :

###############################################################################
 PARAMETER VERSION = 2.1.0


 PORT fpga_0_RS232_RX_pin = fpga_0_RS232_RX, DIR = I
 PORT fpga_0_RS232_TX_pin = fpga_0_RS232_TX, DIR = O
 PORT fpga_0_LED_7SEGMENT_GPIO_d_out_pin = fpga_0_LED_7SEGMENT_GPIO_d_out, DIR = O, VEC = [0:11]
 PORT fpga_0_Push_Buttons_3Bit_GPIO_in_pin = fpga_0_Push_Buttons_3Bit_GPIO_in, DIR = I, VEC = [0:2]
 PORT fpga_0_DIP_Switches_8Bit_GPIO_in_pin = fpga_0_DIP_Switches_8Bit_GPIO_in, DIR = I, VEC = [0:7]
 PORT fpga_0_SRAM_Mem_A_pin = fpga_0_SRAM_Mem_A, DIR = O, VEC = [12:29]
 PORT fpga_0_SRAM_Mem_DQ_pin = fpga_0_SRAM_Mem_DQ, DIR = IO, VEC = [0:31]
 PORT fpga_0_SRAM_Mem_OEN_pin = fpga_0_SRAM_Mem_OEN, DIR = O, VEC = [0:0]
 PORT fpga_0_SRAM_Mem_CEN_pin = fpga_0_SRAM_Mem_CEN, DIR = O, VEC = [0:0]
 PORT fpga_0_SRAM_Mem_CEN_1_pin = fpga_0_SRAM_Mem_CEN, DIR = O, VEC = [0:0]
 PORT fpga_0_SRAM_Mem_WEN_pin = fpga_0_SRAM_Mem_WEN, DIR = O
 PORT fpga_0_SRAM_Mem_BEN_pin = fpga_0_SRAM_Mem_BEN, DIR = O, VEC = [0:3]
 PORT sys_clk_pin = dcm_clk_s, DIR = I, SIGIS = CLK, CLK_FREQ = 50000000
 PORT sys_rst_pin = sys_rst_s, DIR = I, RST_POLARITY = 1, SIGIS = RST
 PORT myleds8_0_leds8_pin = myleds8_0_leds8, DIR = O, VEC = [7:0]
 PORT ExternalPort_0 = net_ExternalPort_0, DIR = O

qui nous dit que les noms que nous devrons utiliser dans le fichier ucf sont "myleds8_0_leds8_pin" !!!! Nous y avons mis quelques points d'exclamation car ce détail nous a bloqué plusieurs heures. Eh bien oui c’est plus facile si l’on vous dit où trouver l'information !

Nous pouvons maintenant modifier le fichier ucf comme suit :

....
#### Module LEDs_8Bit constraints

#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<0> LOC=k12;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<1> LOC=p14;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<2> LOC=l12;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<3> LOC=n14;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<4> LOC=p13;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<5> LOC=n12;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<6> LOC=p12;
#Net fpga_0_LEDs_8Bit_GPIO_d_out_pin<7> LOC=p11;

Net myleds8_0_leds8_pin<0> LOC=k12;
Net myleds8_0_leds8_pin<1> LOC=p14;
Net myleds8_0_leds8_pin<2> LOC=l12;
Net myleds8_0_leds8_pin<3> LOC=n14;
Net myleds8_0_leds8_pin<4> LOC=p13;
Net myleds8_0_leds8_pin<5> LOC=n12;
Net myleds8_0_leds8_pin<6> LOC=p12;
Net myleds8_0_leds8_pin<7> LOC=p11;
....

Reste maintenant à utiliser le périphérique.

Programme C utilisant notre nouveau périphérique

Ici notre périphérique est tellement simple qu’il est facile à programmer. Voici un extrait d'un code possible :

// Located in: microblaze_0/include/xparameters.h
#include "xparameters.h"

#include "stdio.h"

#include "xutil.h"

//==== ajouté car non présent par défaut
#include "xio.h"

//====================================================

int main (void) {
   /*
    * Enable and initialize cache
    */

   #if XPAR_MICROBLAZE_0_USE_ICACHE
      microblaze_init_icache_range(0, XPAR_MICROBLAZE_0_CACHE_BYTE_SIZE);
      microblaze_enable_icache();
   #endif

   #if XPAR_MICROBLAZE_0_USE_DCACHE
      microblaze_init_dcache_range(0, XPAR_MICROBLAZE_0_DCACHE_BYTE_SIZE);
      microblaze_enable_dcache();
   #endif
//=================================
// Sortie sur nouveau PORT 

   XIo_Out32(XPAR_MYLEDS8_0_BASEADDR, 0xAA); // doit allumer une led sur deux

   print("-- Entering main() --\r\n");
// La suite est coupée

Ce programme utilise directement "XIo_Out32" sans utiliser la librairie qui est faite en même temps que le projet.

Voila maintenant décrite ci-dessus la trame complète pour créer un périphérique. Seule notre imagination et la place dans notre FPGA peut constituer un frein au développements de périphériques avancés : liaison PS/2 ou VGA. Pour votre information, l’ensemble réalisé prend 80% de la place dans un FPGA spartan 3 200. Si l’on veut rester raisonnable, il ne faut plus rien mettre dans le FPGA.

Travail à réaliser

Comme d'habitude dans ce livre, ce chapitre sert à l'un des auteurs comme trame de travail pour un projet d'étudiants d'où la présence de cette section.

Projet tutoré

On vous demande d'implanter un microBlaze dans une carte spartan 3. Ce FPGA est un peu petit pour cela mais suffira. On prend cette carte car elle dispose de 4 afficheurs sept segments, de leds, d'interrupteurs, de boutons poussoirs et d'une liaison RS232.

On vous demande de réaliser tous les programmes standards en C sur ce genre de périphériques :

chenillars, compteur sur 4 digits, utilisation de la RS232, de la mémoire RAM.

Le périphérique

Pour cette partie on changera de carte pour avoir un peu plus de place dans le FPGA. On vous demande de développer un périphérique (sans microBlaze) destiné à un jeu appelé Casse-briques sur écran VGA.

Une seule raquette est mobile verticalement. L'autre est présente mais son déplacement est lié à celui de la balle. Donc le rebond sur cette raquette se fait toujours. Comme c’est le processeur qui gère cette raquette droite qui suit la balle, il faut quand même un port de 8 bits pour la déplacer.

Écran VGA définissant notre casse brique

Nous présentons ci-dessus l'écran VGA pour le casse-brique dans l'hypothèse où le déplacement de la raquette liée à la balle est faite par le processeur. Si l’on compare au jeu de Pong du chapitre sur l'ATMega8, il faut 8 bits supplémentaire pour gérer ce nouveau jeu.

Une fois le périphérique réalisé vous devez le connecter au microBlaze.

Connexion du périphérique au microBlaze

La connexion de notre périphérique au microBlaze est un peu plus complexe que dans l'exemple précédent. Voici un schéma représentant le travail à réaliser.

Travail à réaliser pour une connexion (ne fonctionne pas)
Remarque

Nous n'avons pas réussi à faire fonctionner le périphérique comme indiqué dans la figure ci-dessus. Nous n'avons pas testé toutes les possibilités mais nous n'avons jamais réussi à voir apparaître les sorties ajoutée à l'entité globale pour les rendre externes : tout se passait comme si l'outil de synthèse de périphérique ne voyait qu'un périphérique sans sortie physique (ceci toujours avec la version 9.2 qui est la seule pour laquelle nous possédons une licence) !!!

Nous laissons cette figure même si elle ne fonctionne pas pour éviter que quelqu’un fasse la même erreur que nous et pour montrer que le chemin le plus court pour aller d'un point à un autre n’est pas forcément la ligne droite !!!

La seule expérience que nous avions était celle décrite plus haut où toute la partie logique est entièrement dans le fichier user_logic.vhd et nous avons donc décidé de recommencer comme cela (figure ci-dessous) et le miracle s'est produit.

Travail à réaliser pour une connexion (version qui fonctionne correctement)

On peut voir sur la figure que :

  • notre IPCore s’appelle "vgacassbriqu"
  • notre gestion complète du VGA s’appelle "VGATop". Cette partie est supposée être au point.
  • usr_logic est un composant généré automatiquement avec 4 registres 32 bits appelés slv_reg0, slv_reg1, slv_reg2 et slv_reg3. Ce nombre de registres a été fixé à l'étape 1 avec le Wizard.
  • le dessin montre comment ces 32 bits sont reliés aux déplacements de la balle et des raquettes, à la gestion des scores et aux murs de briques. Ces connexions sont extrêmement importantes car elles influenceront directement l'écriture des sous-programmes.

Connexion du périphérique et VHDL

Voici des extraits VHDL qui montrent le travail réalisé. On ajoute d’abord les sorties au périphérique :

entity vgacassbriqu is
  generic
  (
    -- ADD USER GENERICS BELOW THIS LINE ---------------
    --USER generics added here
    -- ADD USER GENERICS ABOVE THIS LINE ---------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol parameters, do not add to or delete
    C_BASEADDR                     : std_logic_vector     := X"FFFFFFFF";
    C_HIGHADDR                     : std_logic_vector     := X"00000000";
    C_SPLB_AWIDTH                  : integer              := 32;
    C_SPLB_DWIDTH                  : integer              := 128;
    C_SPLB_NUM_MASTERS             : integer              := 8;
    C_SPLB_MID_WIDTH               : integer              := 3;
    C_SPLB_NATIVE_DWIDTH           : integer              := 32;
    C_SPLB_P2P                     : integer              := 0;
    C_SPLB_SUPPORT_BURSTS          : integer              := 0;
    C_SPLB_SMALLEST_MASTER         : integer              := 32;
    C_SPLB_CLK_PERIOD_PS           : integer              := 10000;
    C_FAMILY                       : string               := "virtex5"
    -- DO NOT EDIT ABOVE THIS LINE ---------------------
  );
  port
  (
    -- ADD USER PORTS BELOW THIS LINE ------------------
    --USER ports added here

	 hsynch,vsynch,red,green,blue : out STD_LOGIC; -- oui oui c’est bien ici que cela se passe
    -- ADD USER PORTS ABOVE THIS LINE ------------------   
    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol ports, do not add to or delete
    SPLB_Clk                       : in  std_logic;
    SPLB_Rst                       : in  std_logic;
.....

Toujours dans le même fichier on connecte maintenant :

------------------------------------------
  -- instantiate User Logic
  ------------------------------------------
  USER_LOGIC_I : entity vgacassbrik_v1_00_b.user_logic
    generic map
    (
      -- MAP USER GENERICS BELOW THIS LINE ---------------
      --USER generics mapped here
      -- MAP USER GENERICS ABOVE THIS LINE ---------------

      C_SLV_DWIDTH                   => USER_SLV_DWIDTH,
      C_NUM_REG                      => USER_NUM_REG
    )
    port map
    (
      -- MAP USER PORTS BELOW THIS LINE ------------------
      --USER ports mapped here
		hsynch => hsynch, -- c’est ici que cela se passe
		vsynch => vsynch,
		red => red,
		green => green,
		blue => blue,
      -- MAP USER PORTS ABOVE THIS LINE ------------------

      Bus2IP_Clk                     => ipif_Bus2IP_Clk,
-- .............

On ajoute ensuite les sorties à l'entité usr_logic

entity user_logic is
  generic
  (
    -- ADD USER GENERICS BELOW THIS LINE ---------------
    --USER generics added here
    -- ADD USER GENERICS ABOVE THIS LINE ---------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol parameters, do not add to or delete
    C_SLV_DWIDTH                   : integer              := 32;
    C_NUM_REG                      : integer              := 4
    -- DO NOT EDIT ABOVE THIS LINE ---------------------
  );
  port
  (
    -- ADD USER PORTS BELOW THIS LINE ------------------
    --USER ports added here
	 hsynch,vsynch,red,green,blue : out STD_LOGIC;
    -- ADD USER PORTS ABOVE THIS LINE ------------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol ports, do not add to or delete
    Bus2IP_Clk                     : in  std_logic;

Et enfin on connecte tout le monde comme ceci :

------------------------------------------
  -- Example code to drive IP to Bus signals
  ------------------------------------------
  IP2Bus_Data  <= slv_ip2bus_data when slv_read_ack = '1' else
                  (others => '0');

  IP2Bus_WrAck <= slv_write_ack;
  IP2Bus_RdAck <= slv_read_ack;
  IP2Bus_Error <= '0';
  ------------------------------------------
  -- my own signals  
  ------------------------------------------
  x_ball <= slv_reg0(22 to 31);
  y_ball <= slv_reg0(6 to 15);
  y_raqG <= slv_reg1(24 to 31);
  y_raqD <= slv_reg1(8 to 15);
  score <= slv_reg2(24 to 31);
  mur1 <= slv_reg3(24 to 31);
  mur2 <= slv_reg3(8 to 15);
  ------------------------------------------
  -- my own components  
  ------------------------------------------
   USER_LOGIC_II : VGAtop
    port map (
	 clk_50 => Bus2IP_Clk,
		x_rect => x_ball,
		y_rect => y_ball,
		y_raquG => y_raqG,
		y_raquD => y_raqD,
		scoreG => score,
		ligne1 => mur1,
		ligne2 => mur2,
		hsynch => hsynch,
		vsynch => vsynch,
		red => red,
		green => green,
		blue => blue
	 );
	 
end IMP;

Après cela il suffit de connecter le périphérique en ajoutant celui-ci dans notre projet et en ajoutant les sorties VGA dans le fichier ucf :

#### Module VGATop : VGA outputs (spartan 3)
Net vgacassbriqu_0_blue_pin   LOC=R11;
Net vgacassbriqu_0_green_pin  LOC=T12;
Net vgacassbriqu_0_red_pin    LOC=R12;
Net vgacassbriqu_0_vsynch_pin LOC=T10;
Net vgacassbriqu_0_hsynch_pin LOC=R9;
Remarque

Lors de la connexion de votre périphérique demandez un espace d'adressage de 16 et non de 4. Vous avez certes 4 registres mais de 4 octets chacun, cela fait donc un espace d'adressage de 16 octets en tout ! On en profite pour rappeler ici que tout multiple de 16, comme espace d'adressage, conviendrait et même simplifierait la logique de décodage.

Solution pour le fichier usr_logic.vhd

Le fichier usr_logic.vhd est généré automatiquement lors de la création du périphérique. Mais il est modifié comme suit.

Solution pour le fichier vgacassbrik.vhd

Vgacassbrik est le nom de mon périphérique. Ainsi ce fichier est généré automatiquement mais modifié comme suit.

Développement du logiciel

Il vous faut maintenant développer le logiciel pour réaliser le jeu demandé.

La section Partie Logicielle d'un autre projet vous propose un cahier des charges que nous ne reproduisons pas ici pour éviter les doublons.

Voici en attendant un petit bout de programme C qui fait bouger la balle :

#include "xparameters.h"
#include "xio.h" 

int main()
{ Xuint32 i,posXY;
// deplacement de la balle
    posXY = 0x00100010;
    XIo_Out32(XPAR_VGACASSBRIK_0_BASEADDR,posXY);	
	while(1) {
	 XIo_Out32(XPAR_VGACASSBRIK_0_BASEADDR,posXY);
	 posXY++;
	 posXY += 0x00010000;
//	 if ((posXY & 0x000003FF)>600) posXY &= 0xFFFFFC00;
// si dépassement coordonnée balle en bas on revient en 0
	 if ((posXY & 0x03FF0000)>0x019A0000) posXY &= 0xFC00FFFF;
	 for (i=0;i<100000;i++); // attente OK
// déplacement des deux raquettes de 16 pixels vers le bas
          XIo_Out32(XPAR_VGACASSBRIK_0_BASEADDR+4, 0x000F000F);
// Affichage de 99 sur les deux afficheurs sept segments
          XIo_Out32(XPAR_VGACASSBRIK_0_BASEADDR+8, 0x00000099);
// Affichage des deux murs de brique
          XIo_Out32(XPAR_VGACASSBRIK_0_BASEADDR+0xC, 0x00FF00FF);
	}
	return 0;
}

La boucle d'attente fonctionne correctement : elle n'a pas été optimisée par le compilateur.

Remarque

Nos étudiants utilisent une version 9.2 sous XP sous MAC et pour une raison que nous ignorons la boucle d'attente a été optimisée !!! Nous avons été obligé d'ajouter une instruction de sortie de PORT "XIo_Out32" pour éviter cette optimisation !!!

Plusieurs essais de compilations, avec aussi la version 9.2, mais avec un vrai XP n'ont jamais donné d'optimisation pour nous. Les mystères de l'informatique sont vraiment impénétrables.

Le développement du logiciel est un défi en lui-même car il doit tenir sur 16 ko. Pour une architecture 32 bits, 16 ko ce n'est vraiment pas beaucoup !

Solution de la partie logicielle

La solution présentée ci-dessous gère un certain nombre de niveaux, le changement de pente de trajectoire et semble tenir dans un peu plus de ko. Il y a certainement encore bien des choses à développer d'un point de vue logiciel mais ce sera fait un peu plus tard.

Conclusion

Puisque ce projet a été réalisé avec des étudiants de niveau 14, il nous semble important de donner nos impressions. Nous avons en effet classé ce chapitre en niveau 16 !

Les deux étudiants qui ont réalisé ce projet avaient un bon niveau (dans les dix premiers de la promotion). Ainsi la partie matérielle a été réalisée dans les temps (en moins de 30 heures). Il leur restait un peu plus de 30 heures pour réaliser la partie logicielle et force est de constater que ce laps de temps est largement insuffisant pour des étudiants de L2. Une autre façon de dire les choses est que le programme donné en correction dans la section précédente est celui de l'enseignant tuteur. Les étudiants ont réussi à faire bouger la raquette gauche avec des interrupteurs de la carte F.P.G.A., à faire bouger l'autre raquette en fonction de la balle pour avoir un rebond systématique. Les rebonds sur les parois haute et basse sont aussi gérés, ains que l’utilisation de l’Algorithme de tracé de segment de Bresenham pour la trajectoire de balle. Mais pas de gestion des deux murs de briques.

Voir aussi la conclusion du projet identique avec ATMega8.

Vous voila donc arrivé au terme de votre première réalisation d'un processeur 32 bits avec son périphérique associé.

Perspectives

Il existe un LogiCORE IP (appelé MicroBlaze Micro Controller System (MCS)) gratuit chez Xilinx. Sa documentation doit être facile à trouver (ds865_microblaze_mcs.pdf). Sa particularité est que sa mise en œuvre ne nécessite pas XPS, autrement dit, peut être réalisée à partir du Webpack gratuit pour nos étudiants. Ses entrées sorties sont extensibles un peu à la façon du picoBlaze (adresse, données et write/readStrobe), il possède la RS232 et des sources d'interruptions. Nous ignorons cependant à partir de quelle version du Webpack ce logicore est disponible. Après quelques lectures sur Internet, il semblerait que ce soit à partir de la version 13.4 que ce logicore soit disponible. Nous espérons avoir l’occasion de le mettre en œuvre prochainement.

Remarque

Nous avons découvert récemment (Mai 2012) que l'interface PLB mise en œuvre dans ce chapitre est obsolète, ou en tout cas le deviendra bientôt ! La nouvelle interface s’appelle AXI system et nécessitera évidemment des modifications pour le travail d'interface de notre périphérique graphique présenté dans les sections précédentes. Nous attendons que les choses se stabilisent un peu avant de nous lancer dans son utilisation. Sa documentation peut être trouvée dans "AXI Reference Guide".

Voir aussi


Cet article est issu de Wikiversity. Le texte est sous licence Creative Commons - Attribution - Partage dans les Mêmes. Des conditions supplémentaires peuvent s'appliquer aux fichiers multimédias.