Very High Speed Integrated Circuit Hardware Description Language/Le MicroBlaze

Leçons de niveau 17
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Very High Speed Integrated Circuit Hardware Description Language : Le MicroBlaze
Very High Speed Integrated Circuit Hardware Description Language/Le MicroBlaze
 », n'a pu être restituée correctement ci-dessus.

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.

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".


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.

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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.

Lancer BSB (Base System Builder)[modifier | modifier le wikicode]

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


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...[modifier | modifier le wikicode]

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.


Tutoriel pour carte spartan3E[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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


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ébut d’un principe
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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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.

Début d’un principe
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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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

Notre cahier des charges[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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"


Détails sur l'étape 2 : vérifier la syntaxe du périphérique[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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;


Résumé schématique du travail réalisé[modifier | modifier le wikicode]

Voici schématiquement ce que nous avons fait :

Travail à réaliser pour une connexion
Début d’un principe
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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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é[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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)

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[modifier | modifier le wikicode]

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;


Solution pour le fichier usr_logic.vhd[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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.


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[modifier | modifier le wikicode]

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 4 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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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.


Voir aussi[modifier | modifier le wikicode]