Very High Speed Integrated Circuit Hardware Description Language/Programmer in Situ et déboguer

Début de la boite de navigation du chapitre

Nous avons l'intention d'examiner dans ce chapitre toutes les techniques permettant de programmer et déboguer in Situ. Tous les projets réalisés dans les chapitres précédents nécessitaient une compilation complète de tout le projet chaque fois qu'un programme C était changé. Cela était parfois très long (10 min sur de vieux PCs) et donc assez contraignant. Il est ainsi intéressant de passer en revue diverses techniques pour simplement envoyer un programme dans la RAM du FPGA sans recompiler l’ensemble du projet. Dans l'univers des Microcontrôleurs existants, on appelle cela la programmation in-situ (In-System Programming : ISP dans la suite de ce document). Chez Atmel ou chez MicroChip par exemple l'ISP (In-System Programming) se décline en deux façons différentes : le SPI (Serial Peripheral Interface) et le JTAG. Cet ensemble est aussi parfois appelé (en) In Circuit Serial Programming (ICSP). Les fichiers envoyés seront au format HEX (Intel) ou binaire.

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 : Programmer in Situ et déboguer
Very High Speed Integrated Circuit Hardware Description Language/Programmer in Situ et déboguer
 », n'a pu être restituée correctement ci-dessus.

Après avoir examiné ces problèmes de programmation, nous nous tournerons vers les problèmes de débogage certainement encore plus complexes.

Introduction (pour Xilinx)

modifier

Même si nous avons déjà eu l’occasion d'évoquer ce sujet dans une annexe du chapitre sur l'AVR ATMega8, nous pensons qu'une deuxième explication ne sera pas de trop. Regardez attentivement la figure ci-dessous :

 
Les chaines de compilations matérielle et logicielle sont séparées

Elle montre clairement que la chaîne de compilation matérielle (à gauche) n'a aucun lien avec la chaîne de compilation logicielle (à droite). Autrement dit, l'ISE de Xilinx est incapable de mettre un programme logiciel compilé en mémoire matérielle. Comment peut-on modifier ce fait ?

La méthode généralement utilisée consiste à proposer un utilitaire capable de transformer le résultat d'une compilation en un fichier VHDL. Les débutants en VHDL ont un peu de mal à se faire à cette idée : quel est le rapport entre VHDL et mémoire ? Pour eux, nous allons proposer plus loin dans ce chapitre une section qui examine la réalisation de mémoire en VHDL. Voici en image un exemple encore lié à l'ATMega8.

 
Les chaines de compilations matérielle et logicielle enfin reliées

Qu'a-t-on ajouté ? On a ajouté un programme externe "make_mem" capable de transformer un fichier HEX en fichier VHDL qui sera inséré dans le projet (pour l'exemple avec le fichier mem_content.vhd). Cette façon de faire est à mon avis la plus simple et tout projet devrait la proposer au moins pour les débutants. Mais elle est cependant loin d’être un must. En effet tout changement dans le programme C nécessite de réaliser la compilation des programmes C ce qui prend quelques secondes puis de transformer le fichier HEX en fichier VHD ce qui ne prend aussi que quelques secondes et enfin de compiler le projet VHDL en entier ce qui peut prendre un certain temps (10 - 15 min sur un vieux PC). Lancer ISE pour compiler un projet important est toujours consommateur de temps. Il nous faut donc explorer d'autres solutions et c’est bien l'objectif de ce chapitre.

Présentation générale des techniques de base

modifier

Pour ne pas tout réinventer et rester dans l'univers de ce qui se fait couramment avec les micro controleurs modernes, nous voyons diverses façons de procéder pour envoyer un programme compilé dans notre FPGA :

  1. utiliser le protocole ISP existant (sur certains processeurs),
  2. utiliser la RS232,
  3. utiliser l'interface JTAG.

La mise en œuvre d'un de ces protocoles consiste donc à ajouter un module capable de dialoguer avec un PC pour remplir les mémoires. Cela consiste à intervenir directement dans le FPGA : votre fichier BIT est téléchargé dans votre FPGA et vous voulez vous servir d'une liaison quelconque pour y mettre un programme. Mais un petit coup d’œil à la figure précédente montre qu’il y a un autre point d'entrée : le fichier d’extension ".bit". C'est ce que nous allons examiner maintenant.

Avant de commencer, un petit détour par data2mem

modifier

"data2mem" est un utilitaire fourni par Xilinx pour résoudre les problèmes de compilation. Il est capable de prendre des valeurs hexadécimales et les mettre dans la BRAM du FPGA. En clair, il est capable de prendre un fichier matériel compilé (d'extension ".bit") et d'y insérer des valeurs qui finiront dans la BRAM lors du téléchargement du fichier ".bit". Cette solution réduit elle aussi le temps de compilation matérielle : vous compilez votre logiciel et data2mem vous insère le résultat dans le fichier ".bit" que vous n'êtes donc pas obligé de refaire en entier. Pourtant, même si nous avons l'intention d'essayer cette solution, nous préférons aller de l'avant et explorer d'autres solutions. La raison est simple :

  1. "data2mem" est un outil en ligne de commande, donc délicat à maîtriser
  2. "data2mem" est un outil Xilinx, donc propriétaire dont on ne connaît pas le code source. Le forum de Xilinx indique même que certaines versions de l'ISE ont un data2mem comportant des bogues (surtout pour les spartan6 !!!)
  3. "data2mem" nécessite une connaissance des BRAM approfondie ou en tout cas connaître la façon dont votre BRAM est utilisée par votre outil de synthèse.

Pour ceux qui ne sont pas convaincu par la première raison indiquée, voici le résultat affiché quand on le lance :

[smoutou@localhost lin]$ ./data2mem

Release 11.1i - Data2MEM L.33, build 1.5.8 Jul 23, 2008
Copyright (c) 1995-2011 Xilinx, Inc.  All rights reserved.


Data2MEM  <-bm FILENAME [.bmm]> |
          <<[-bm FILENAME [.bmm]]>
           <-bd FILENAME [<.elf>|<.mem>] [<[<boot [ADDRESS]>] tag TagName <TagName>...>]>...
           <-o <u|v|h|m> FILENAME [.ucf|.v|.vhd|.mem]>
           <-p PARTNAME>
           -i>> |
          <<-bd FILENAME [.elf]> -d [e|r]> [<-o m FILENAME [.mem]>]>> |
          <<-bm FILENAME [.bmm]>
           <-bd FILENAME [<.elf>|<.mem>] [<[<boot [ADDRESS]>] tag TagName <TagName>...>]>...
           <-bt FILENAME [.bit]> <-o b FILENAME [.bit]>> |
          <<-bm FILENAME [.bmm]> <-bt FILENAME [.bit]> -d>> |
          <-bx [FILEPATH]> |
          <-mf <p PNAME PTYPE PID <a SNAME MTYPE ASTART BWIDTH <s BSIZE DWIDTH IBASE>...>...>...>> |
          <<-pp FILENAME [.bmm]> <-o p FILENAME [.bmm]>> |
          <-f FILENAME [.opt]> |
          <-w [on|off] > |
          <-q [s|e|w|i]> |
          <-intstyle silent|ise|xflow> |
          <-log [FILENAME [.dmr]]> |
          <-u> |
          <-h [ <option [< option>...]> | support ]>


-h option_list | all | sup[port]
     Print help text for command line options.  If only a '-h' is given, then
     only the option summary is output. If the '-h' is followed by a space
     separated list of option names (without the dash), help is output for
     the listed options.  For example, '-h bm bd i' gives help on the 'bm',
     'bd', and 'i' options.  Use '-h all' for help on all options.  Lastly,
     use '-h support' to list the devices Data2MEM supports.  The
     keyword 'support' can be abbreviated up to 'sup'.

De quoi refroidir quelques ardeurs non ?

L'environnement Microblaze XPS (Xilinx Platform Studio) utilise data2mem, comme vous pouvez le constater dans le chapitre sur le MicroBlaze de ce livre.

On le trouve aussi dans un autre projet openMSP430, sous la forme :

data2mem -bm ../memory.bmm -bd $1.elf -bt openMSP430_fpga.bit -o b $1.bit

qui montre que l’on peut charger des fichiers au format elf (ce qui était vrai aussi pour le MicroBlaze). Cette commande nous fait aussi deviner la présence d'un fichier spécial d'extension .bmm, qui est un fichier pour décrire où et comment la ROM est implanté dans les BRAM (Bloc RAM). Et c’est là que réside le problème. Nous avons utilisé cette technique pour deux processeurs et la réalisation du fichier .bmm n'a jamais été une partie de plaisir. Nous avons même échoué sur un troisième projet. Il nous manque certainement de l'expérience pour comprendre quelques subtilités mais préférons passer du temps à utiliser les SOCs plutôt que de devenir spécialiste de data2mem ! La réalisation automatique de ce fichier .bmm est décrite un peu plus loin dans ce chapitre.

Il existe aussi un outil sous DOS/windows pour faire cela comme expliqué dans la section suivante pour le cas particulier du picoBlaze mais nous ne l'avons jamais utilisé.

Utiliser la RS232 pour programmer

modifier

Nous avons déjà eu l’occasion d'examiner la RS232 dans un chapitre précédent. Nous allons donc utiliser les résultats de ce chapitre pour réaliser une programmation à travers la RS232. L'avantage de cette technique est qu’il n’est pas difficile de trouver pour n’importe quel système d'exploitation (Windows, (GNU) Linux, Mac OS X ou autres) un logiciel capable d'envoyer et/ou recevoir des fichiers par une liaison série qui passe éventuellement par l’USB.

Utiliser un cœur Série asynchrone pour programmer

modifier

Il existe deux projets chez OpenCores qui ont des fonctions élémentaires de programmation par la liaison série. Ces cœurs peuvent être utilisés pour remplacer un bootloader. Leur mise en œuvre est un peu plus générale que ce que l’on vient de faire mais cela nécessite un peu de réflexion. Il faudra en effet partager la mémoire entre le processeur et le cœur série. Pour les trouver chez Opencores :

  1. Dans les projets chercher "Communication contoller" puis "UART to Bus" de Litochevski Moti et Muller Steve
  2. Dans les projets chercher "(en) System controller" puis "RS232 System Controller" de John Clayton.

Le premier projet est disponible en Verilog et en VHDL et relativement compact. Le deuxième projet est en verilog et a servi pour développer un processeur.

Développons un peu l’utilisation pratique de ces deux cœurs à partir de leur documentation (nous ne les avons pas mis en œuvre).

Projet UART to Bus
modifier

Ce projet est destiné à réaliser une communication série permettant de remplir ou de lire une mémoire. Il comporte deux modes distincs :

  1. un mode texte où commandes et données sont envoyés en caractères ASCII donc facile à utiliser avec un hyperterminal
  2. un mode binaire où commandes et données sont envoyées et reçues en binaire.

Les deux seules commandes sont :

  • 'W' ou 'w' pour écriture avec la syntaxe : "W 0D 16FFRC" où RC désigne le retour Chariot réalisé par LF ou CR. Cette commande écrit 0x0D en adresse 0x16FF.
  • 'R' ou 'r' pour lecture avec la syntaxe "R 1A2FRC" où RC désigne le retour Chariot réalisé par LF ou CR. Cette commande retourne le contenu d'une mémoire en adresse 0x1A2F.
Projet RS232 System Controller
modifier

En plus des commandes du projet précédent (write et read) une nouvelle commande 'i' pour initialisation qui est un reset est présente. Ce projet nécessite un bus Whishbone qui peut être partagé entre un processeur et ce cœur ce qui en fait un outil de choix pour développer un processeur en VHDL.


Utiliser l'interface Série synchrone pour programmer directement

modifier

L'interface In System Programming (ISP) peut se traduire par "programmation à même le circuit". Dans ce chapitre nous garderons la terminologie anglo-saxonne.

Ce type de programmation des Microcontrôleurs est assez généralisée. On la trouve bien entendu dans les AVR de chez Atmel et dans les microcontôleurs PIC.

Ce système est tiré du protocole Serial Peripheral Interface : certains constructeurs respectent le nom des broches de ce protocole, d'autres non.

Utiliser le protocole In-System Programming (ISP) existant est probablement très attractif. Cela permettrait par exemple d’utiliser un environnement de développement comme AVRStudio de compiler avec cet environnement et de télécharger directement dans le FPGA, tout cela de manière transparente. Mais le protocole utilisé ne dépend-t-il pas des fabricants et voir des familles chez un même fabricant ? La réponse est évidemment affirmative. Cela lui enlève beaucoup d’intérêt du coup : vous allez passer beaucoup de temps à développer un module ISP pour un AVR (par exemple), mais ce même module sera incapable de dialoguer avec MPLAB et un PIC !

Nous présentons quand même un cœur qui permet de ne pas partir de rien !

Un cœur chez OpenCore

modifier
  • spi master slave de Doin Jonny est probablement un cœur à examiner pour continuer ce chapitre. Sa description est traduite maintenant.

Doin Jonny, auteur de ce cœur écrit : Ce projet est parti du besoin d’avoir une simple interface SPI écrite en VHDL pour utiliser un interfaçage FPGA/circuit générique.

Le cœur ainsi réalisé génère un circuit petit et efficace capable d'opérer avec des horloges lentes jusqu'à des horloges de 50 MHz.

Ce projet comporte deux cœurs indépendants : SPI_MASTER et SPI_SLAVE le maître et l'esclave.

Les deux cœurs sont écrits en VHDL avec une architecture pipeline et des domaines d'horloge bien séparés pour l'horloge du bus SPI et celle de l'interface parallèle.

La conception ciblait originellement les Spartan-6 de chez Xilinx mais est écrit en code synthétisable indépandant de la téchnologie. Les cirsuits préservent les ressources d'horloge du FPGA en utilisant directement l'horloge rapide pour toutes les bascules avec une entrée "clock enables" (CE) pour les registres.

Le maître et l'esclave ont été vérifiés en hardware en utilisant la carte "Digilent Atlys board (Spartan-6 @100 MHz)" avec une horloge SPI de 500 kHz à 50 MHz, avec aucun déphasage et des opérations robustes.

Si vous trouvez ce cœur utile, faites le moi savoir: jdoin@opencores.org

Performances

  • cœur VHDL, complètement synchrone, conçu avec une architecture RTL pipelinée classique, avec une horloge globale grande vitesse
  • Interface SPI très petite et efficace
  • paramétrisable avec des generics : (N, CPOL, CPHA, PREFETCH, SPI_2X_CLK_DIV)
    • modes SPI (CPOL, CPHA): supporte les modes 0,1,2,3
    • largeur des mots (N): de 8 bits à le limite de synthèse (accepte n’importe quelle largeur de mots)
    • Lookahead input data request (PREFETCH): pipelined data request for back-to-back data transmission
    • SPI diviseur par 2x de l'horloge rapide système
  • Très économique : pas de FIFO, juste un buffer de sortie parallèle pour les données reçues
  • Lecture et écriture parallèles similaires à de la RAM
  • domaines d'horloges indépendants pour le bus série et la lecture et l'écriture du bus parallèle avec des pipelines de transferts
  • Peut être utilisé pour contrôler des cirsuits généraux SPI (maîtres) ou comme interface à des unités centrales (esclaves)
  • Indépendant du fondeur, entièrement conçu en LUT/FF, n'utilise aucune structure spécifique à Xilinx, IOBs ou registres à décalage
  • Synthetise jusqu'à +210 MHz dans une Spartan-6 (lowest grade), en utilisant seulement de la logique CLB
  • Vérifiée en silicium avec une horloge à 100 MHz, utilisant des fréquences SPI de 500 kHz jusqu'à 50 MHz dans une Spartan-6 XC6SLX45-2
  • Très petite: 41 slices pour 2 ports (une interface maître + une interface esclave) avec 32 bits de largeur de mots

Signalons qu'un autre chapitre de ce livre utilise ce cœur SPI (partie esclave de ce cœur) dans un domaine complètement différent (Communication entre processeur et FPGA).


Avant de passer au vif du sujet, il nous faut examiner là où l’on a l'intention de mettre le programme, j’ai nommé les mémoires.

Mémoire et FPGA

modifier

Vous pouvez lire la section RAM et FPGA du wikibook Conception et VHDL qui vous montre un exemple d'utilisation de RAM en VHDL.

Tous les FPGA modernes proposent deux sortes de RAM : une mémoire bloc (appelée BRAM dans la suite de ce document) et une mémoire distribuée (appelée DRAM dans la suite de ce document).

Si vous voulez écrire dans ces mémoires en VHDL, il vous faut respecter un certain nombre de règles pour que votre outil de synthèse infère la présence de mémoires. S'il ne le fait pas, il va remplacer la mémoire par de la logique traditionnelle, ce qui est très couteux en termes de ressources. Le problème est que la façon de faire comprendre que vous avez de la RAM est très dépendant du fabricant du FPGA. Nous allons nous contenter de présenter les techniques associées au constructeur Xilinx pour le moment.

RAM prioritaire à l'écriture

modifier

Voici comment procéder si l’on veut cibler un composant Spartan3 :

--Single-Port RAM in Write-First Mode VHDL Coding Example One
-- spartan 3
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity rams_02a is
    port (clk : in std_logic;
           we    : in std_logic;
           en    : in std_logic;
           addr : in std_logic_vector(5 downto 0);
           di    : in std_logic_vector(15 downto 0);
           do    : out std_logic_vector(15 downto 0));
end rams_02a;
architecture syn of rams_02a is
    type ram_type is array (63 downto 0)
          of std_logic_vector (15 downto 0);
    signal RAM : ram_type;
begin
    process (clk)
    begin
        if clk'event and clk = '1' then
              if en = '1' then
                  if we = '1' then
                      RAM(conv_integer(addr)) <= di;
                      do <= di;
                  else
                       do <= RAM( conv_integer(addr));
                  end if;
              end if;
         end if;
    end process;
end syn;

Et maintenant voici comment procéder pour un composant spartan3E. Pour ce composant il semble qu'une RAM à lecture prioritaire soit plus adaptée :

--Single-Port RAM in Read-First Mode VHDL Coding Example
-- spartan 3E
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity rams_01 is
    port (clk    : in std_logic;
           we    : in std_logic;
           en    : in std_logic;
           addr  : in std_logic_vector(5 downto 0);
           di    : in std_logic_vector(15 downto 0);
           do    : out std_logic_vector(15 downto 0));
end rams_01;
architecture syn of rams_01 is
    type ram_type is array (63 downto 0) of std_logic_vector (15 downto 0);
    signal RAM: ram_type;
begin
    process (clk)
    begin
        if clk'event and clk = '1' then
              if en = '1' then
                  if we = '1' then
                      RAM(conv_integer(addr)) <= di;
                  end if;
                  do <= RAM(conv_integer(addr)) ;
              end if;
         end if;
    end process;
end syn;

Il serait intéressant de mesurer le coût d'une erreur à ce niveau : que se passe-t-il si l’on ne respecte pas les conseils Xilinx : utiliser un modèle spartan3E sur un spartan3 ?

Nous renvoyons le lecteur à la documentation officielle Xilinx pour qu’il se fasse sa propre idée.

Une mémoire morte

modifier

Une mémoire morte est simplement une RAM initialisée. Cette définition est très discutable, mais ce que l’on cherche à dire, c’est que nous utiliserons ce genre de mémoire pour mettre un programme dedans. Il y a bien longtemps, ce rôle était dévolu aux ROMs ou EPROMs puis aux EEPROMS. Aujourd’hui les processeurs ont des instructions pour écrire dans la mémoire programme : celle-ci est donc, comme une RAM, à écriture ou lecture. Nous donnons un exemple VHDL maintenant :

--Single-Port BRAM Initial Contents VHDL Coding Example
--
-- Initializing Block RAM (Single-Port BRAM)
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity rams_20a is
    port (clk : in std_logic;
          we : in std_logic;
          addr : in std_logic_vector(5 downto 0);
          di : in std_logic_vector(19 downto 0);
          do : out std_logic_vector(19 downto 0));
end rams_20a;
architecture syn of rams_20a is
   type ram_type is array (63 downto 0) of std_logic_vector (19 downto 0);
   signal RAM : ram_type:= (X"0200A", X"00300", X"08101", X"04000", X"08601",  X"0233A",
                             X"00300", X"08602", X"02310", X"0203B", X"08300", X"04002",
                             X"08201", X"00500", X"04001", X"02500", X"00340", X"00241",
                             X"04002", X"08300", X"08201", X"00500", X"08101", X"00602",
                             X"04003", X"0241E", X"00301", X"00102", X"02122", X"02021",
                             X"00301", X"00102", X"02222", X"04001", X"00342", X"0232B",
                             X"00900", X"00302", X"00102", X"04002", X"00900", X"08201",
                             X"02023", X"00303", X"02433", X"00301", X"04004", X"00301",
                             X"00102", X"02137", X"02036", X"00301", X"00102", X"02237",
                             X"04004", X"00304", X"04040", X"02500", X"02500", X"02500",
                             X"0030D", X"02341", X"08201", X"0400D");
begin
  process (clk)
  begin
    if rising_edge(clk) then
      if we = '1' then
       RAM(conv_integer(addr)) <= di;
      end if;
    do <= RAM(conv_integer(addr));
    end if;
  end process;
end syn;

La première fois que nous avons implanté ce type de programme nous pensions que ce type de mémoire était forcément implantée en BRAM mais comme nous l'expliquons dans un autre livre, ce n’est pas toujours le cas. Cela peut dépendre en fait de la version de l'ISE utilisée !

Technologie Xilinx

modifier

Tout ce qui a été fait dans ce début de chapitre sur les mémoires est relativement portable : le code est du vrai VHDL et l’on peut donc penser que n’importe quel environnement de développement l'acceptera. Nous allons nous attacher à un cas particulier de mémoires propres à Xilinx. Vous rencontrerez plusieurs fois le bandeau de définition ci-dessous dans ce document mais cette terminologie est très importante.

À notre connaissance ces RAMB16 existent encore dans les FPGA plus modernes comme les Spartan 6. Par contre ils peuvent avoir jusqu'à 4 PORTs indépendants mais certains sont limités en lecture seulement. Cette augmentation du nombre de PORTs indépendants permet d’éviter d’utiliser de la DMA. Nous avons eu l’occasion de partager une RAM entre un processeur et une petite carte vidéo par exemple.


Projet picoBlaze

modifier

Cette section est dédiée au picoBlaze et à toutes les méthodes que l’on peut envisager pour le programmer.

Utiliser data2mem en sept étapes pour le PicoBlaze

modifier

Cette section est une traduction d'un document écrit par Ken Chapman (qui a conçu le picoBlaze) mais nous ne l'avons pas encore testée.

  • Faire votre implantation avec l'ISE, cela vous donnera 3 fichiers importants parmi d'autres :
    • design_name.ncd - fichier général de données pour le FPGA
    • design_name.bit
    • program.psm - programme en assembleur pour PicoBlaze.
  • Copier les fichiers 'PB_BMM.EXE' et 'change_pb_bits.bat' dans votre répertoire de travail.
  • Opérer les changements de votre programme.
  • Ouvrir une fenêtre DOS dans votre répertoire de travail.
  • Exécuter le fichier bat en utilisant la commande (noter l'absence d'extensions)...
change_pb_bits progname design_name
  • En supposant un assemblage correct de votre programme, vous verrez apparaître les noms d'instance de tous les blocs mémoire de votre conception. Entrer le nombre de BRAM associé à votre programme.
  • Un nouveau fichier .bit sera automatiquement généré mais précédé par 'new_' (e.g. new_design_name.bit) que vous pouvez charger avec Impact (outil de chargement dans le FPGA).


Utiliser la RS232 pour remplir la mémoire programme du picoBlaze

modifier

Nous avons l'intention dans ce chapitre d'examiner la possibilité de remplir la mémoire programme du PicoBlaze à l'aide de la liaison série. Ce problème est très lié à Xilinx et nous utiliserons donc les logicores du chapitre sur la RS232 précédent.

Nous avons fini par abandonner l’utilisation du picoBlaze. Cette section restera donc comme cela sauf si un lecteur veut bien la compléter.

Projet coreATMega8

modifier

Le coreATMega8 est décrit dans le chapitre Embarquer un Atmel ATMega8 et sa chaine de compilation est particulièrement examinée dans Annexe I (transformer un fichier HEX en VHDL). Nous reproduisons sa figure correspondante ici :

 
Les chaines de compilations pour éviter les longues compilations

Il est facile de rester coi devant cette figure en n'en comprenant pas vraiment le sens. Il y a un aspect séquentiel qu'il est difficile de représenter :

  • d'abord j'utilise l'ISE pour réaliser le fichier d'extension .bit
  • je compile le code source en c pour faire le fichier .elf
  • j'utilise directement data2mem pour insérer le programme .elf au bon endroit dans le fichier .bit (à l'aide du fichier .bmm)
  • je télécharge le fichier .bit avec Impact ou autre

Seules les trois dernières étapes qui sont très rapides sont nécessaires pour modifier un programme si la partie matérielle est déjà réalisée.

Le problème évoqué à la section précédente est de réaliser le fichier memory.bmm (qui apparait dans la figure ci-dessus) et qui correspond à notre processeur softcore ! Pour éviter de chercher nous le donnons tout construit :

// Pour spartan3 et spartan3E seulement
ADDRESS_SPACE memory_AVR_rom0 RAMB16 [0x00000000:0x00003FFF] 
  BUS_BLOCK    
     cpu/opcf/pmem/pe_1 [7:4] PLACED = X1Y2;
     cpu/opcf/pmem/pe_0 [3:0] PLACED = X0Y3;
     cpu/opcf/pmem/pe_3 [15:12] PLACED = X1Y3;
     cpu/opcf/pmem/pe_2 [11:8] PLACED = X0Y2;   
     cpu/opcf/pmem/po_1 [23:20] PLACED = X1Y0;
     cpu/opcf/pmem/po_0 [19:16] PLACED = X0Y0;
     cpu/opcf/pmem/po_3 [31:28] PLACED = X1Y1;
     cpu/opcf/pmem/po_2 [27:24] PLACED = X0Y1;
   END_BUS_BLOCK;
END_ADDRESS_SPACE;
 
Les blocs mémoires utilisés dans le FPGA

Ce sont les valeurs derrière le mot clé "PLACED" qu’il faut être capable de retrouver. Nous avons eu l’occasion d’utiliser deux versions de l'outil de synthèse et les deux versions nous ont donné des valeurs de placement différentes (pour un même FPGA). Nous les avons trouvé à l'aide de l'outil PlanAhead (visualisation des cellules FPGA utilisées) en cherchant dans les netlists les RAMB4_S4_S4 correspondant au programme.

Un exemple PlanAhead est présenté ci-contre : le principe est simple vous cherchez les BRAM dans la partie gauche (les netlists) et si vous cliquez dessus on vous présente en blanc dans le FPGA où se trouve le composant en question. Et mieux encore dans une partie non présentée dans la figure, on vous donne la localisation exacte dans le FPGA, autrement dit les valeurs dont vous avez besoin pour mettre après le mot clé "PLACED".

C'est le genre de travail qu’il est difficile à faire quand on débute, surtout que PlanAHaed est, à mon avis, assez mal documenté.

Voici en image la description de ce que je viens de dire : trouvez les bulles dans l'ordre, "je choisis ici", "Je suis ici" et "J’ai besoin de cela". Par rapport à l'exemple précédent on a ajouté la partie très utile pour trouver l'emplacement physique des ROMs qui nous montre que la première "RAMB4_S4_S4" est en "X0Y0".

 
Comment trouver l'endroit où est implanter une ROM ?



Une fois ce fichier réalisé, il faut le retirer du projet : il n'y a donc pas lieu de garder de fichier bmm dans le projet mais par contre il serait bon de transformer les positions trouvées dans le fichier bmm en contraintes dans le fichier ucf pour éviter toute mauvaise surprise.

Utiliser des RAMB16_S4_S4 avec data2mem

modifier

Finalement après avoir tourné en rond pendant près d'un mois (heureusement pas à plein temps), nous avons abandonné l’utilisation des RAMB4_S4_S4 propres au Spartan2 pour utiliser les RAMB16_S4_S4 du spartan 3 mais ceci a comme conséquence une modification de certains fichiers du cœur. Le fichier BMM donné plus haut correspond à cette option.

En 2011, nous avions laissé les codes sources de cette modification directement dans cette section. Mais nous avons décidé de les retirer car nous avons fait trop de modifications en dehors des fichiers qui étaient ici.


Après téléchargement vous trouverez le cœur complet. Il ne doit contenir que 19 fichiers (18 en vhdl et 1 seul ucf) Il y a plus que 19 fichiers fournis, mais vous devrez choisir un seul parmi io.vhd io_timer.vhd io_corcic.vhd à mettre dans le projet. De même un seul ucf sera à utiliser. Dans le sous-répertoire soft vous avez quelques scripts qui utilisent data2mem et des fichiers sources. Il vous faudra adapter les scipts :

  • à votre FPGA spartan3, spartan3E ou spartan6 (is sont pour spartan6 par défaut)
  • à votre système d'exploitation (ceux fournis ne fonctionnent que pour LINUX).


Petit résumé de ce que nous savons faire à ce point

modifier

Votre partie matérielle est complètement réalisée et compilée. Vous avez donc à votre disposition un fichier BIT et l'outil data2mem va être chargé de modifier le fichier BIT à partir d'une compilation de votre nouveau programme source supposé être en C pour la suite des explications.

  • Nous commençons par compiler notre programme c pour obtenir un fichier au format ELF :
avr-gcc -g -mmcu=atmega8 -Wall -Os -c hello.c
avr-gcc -g -mmcu=atmega8 -o hello.elf -Wl,-Map,hello.map hello.o
  • Nous écrivons notre ficher BMM donné plus haut à l'aide d'un éditeur de texte
  • nous lançons data2mem :
data2mem -bm memory.bmm -bd hello.elf -bt avr_fpga.bit -o uh avr_fpga

Pour nous un nouveau fichier BIT est créé : avr_fpga_rp.bit.

Ce nom est probablement un nom par défaut de data2mem puisque nous n'utilisons pas l'option "-o b" pour donner un nouveau nom. L'option proposée ici "-o h" génère un fichier VHDL qui nous a été utile pour construire le fichier BMM ! Si donc vous remplacez "-o h" par "-o b" votre fichier BIT créé s'appellera "new_avr_fpga.bit".

  • Le fichier avr_fpga_rp.bit est chargé dans le FPGA

Un petit script peut faire le travail complet :

#!/bin/bash
#pour spartan6
#export PATH=$PATH:/usr/local/avr/bin:~/XILINX/Xilinx/11.1/ISE/bin/lin/
export PATH=$PATH:/opt/Xilinx/14.5/ISE_DS/ISE/bin/lin64
avr-gcc -g -mmcu=atmega8 -Wall -Os -c hello.c 
avr-gcc -g -mmcu=atmega8 -o hello.elf -Wl,-Map,hello.map hello.o 
avr-objdump -h -S hello.elf > hello.lss
#avr-objcopy -O binary -R .eeprom hello.elf hello.bin
#avr-objcopy -R .eeprom -O ihex hello.elf hello.hex
data2mem -bm memoryS6.bmm -bd hello.elf -bt avr_fpga.bit -o uh avr_fpga
djtgcfg prog -d Nexys3 --index 0 --file avr_fpga_rp.bit

à condition d’avoir ADEPT installé (ADEPT est l'utilitaire de Digilent pour télécharger dans le FPGA).

Utiliser la RS232 et un bootloader pour ATMega8

modifier

Note : Cette section ne débouche malheureusement pas sur un ensemble fonctionnant correctement. Nous allons abandonner sa rédaction pour un temps comme nous l'avons fait déjà l'année précédente à la même époque pour nous consacrer à d'autres parties (de ce livre). Ce que nous espérons faire dans le futur :

  • Réimplanter l'instruction SPM pour qu'elle soit plus conforme à celle des ATMega (Nous avons fait un pas en avant le 7 juillet 2015 après deux ans (pas à plein temps).... Soyez donc patient)
  • Faire fonctionner le bootloader Arduino

Avant de s'intéresser à notre problème particulier, nous allons passer en revue le problème du bootloader dans les Atmel AVR commercialisés. Le mot bootloader est traduit par Chargeur d'amorçage dans Wikipédia mais nous n'utiliserons pas cette terminologie.

Le bootloader dans les AVR

modifier

L'idée est de réaliser ce que l’on trouve dans le petit système de développement appelé (en) Butterfly ou dans l’Arduino. Ces systèmes très bon marché sont architecturés autour d'un bootloader, c'est-à-dire un petit programme qui démarre le processeur en attendant qu'un ensemble de données lui arrive par la RS232. S'il y a des données, elles correspondent à un programme et le bootloader sera chargé de modifier la mémoire programme en conséquence. Tout ceci est bien plus facile à dire qu’à faire. Nous voyons un certain nombre de questions auxquelles il nous faut répondre pour continuer ce travail :

  • le programme bootloader est-il dans un endroit particulier ?
  • peut-on envoyer directement un fichier hex ?

Pour la première question, oui le bootloader est dans un endroit particulier qui semble dépendre de la famille des AVR. Un bootloader de l'Arduino pour l'ATMega8 contient

--section-start=.text=0x1C00 

dans son makefile, ce qui montre très clairement qu’il est à un endroit très précis. En général le bootloader est en mémoire haute. Sa taille est classiquement 1 ou 2 ko.

Pour la deuxième question, il n'y a pas de réponse générale. Tout dépend du protocole, donc du code source du bootloader (et du programmateur). Par exemple le protocole STK500 envoie les programmes à écrire dans la mémoire flash en binaire. C'est le programmateur sur PC qui s'occupe de la transformation hex vers binaire (pour nous avrdude)

Nous pensons que les explications données sont suffisantes pour une compréhension de ce qui va suivre. Si vous voulez d'autres informations plus générales nous avons créé un chapitre dédié aux bootloaders dans les AVR (pas très avancé) dans un autre projet : Les microcontrôleurs AVR.

Avant Juillet 2015

modifier

L'instruction SPM a été implantée en 2011 mais de manière trop différente de celle de l'ATMega8 du commerce pour envisager l’utilisation d'un bootloader existant. Voici comment elle était implantée :

Début d’un principe
Fin du principe


C'est ce dernier point sur le registre R1 qui sera changé un petit peu plus loin dans ce chapitre.

Mais ...

  La version téléchargeable chez OpenCore ne gère pas l'instruction SPM !!! en tout cas pour le moment (14 juillet 2011 à 14:21 UTC) et c’était encore le cas en Juillet 2015 : Juergen Sauermann, auteur du cœur, nous a dit qu’il ne pouvait plus accéder pour modification au site Opencores.org. Nous continuerons à gérer toutes ces versions avec notre site personnel, mais il nous faudra bien un jour trouver une autre solution !


Intéressons-nous maintenant à la façon dont nous allons réaliser les choses dans notre coreATMega8.

Protocole STK500v1 de l'Arduino

modifier

Nous avons comme rêve de réaliser un protocole STK500 et un bootloader correspondant dans notre cœur ATMega8. L'intérêt serait de programmer notre FPGA directement à partir de l'environnement Arduino ou même de l'environnement AVRStudio (remplacé par MPLABX depuis l'achat d' ATMEL par Microchip).

À ce jour nous n'avons pas réussi à le réaliser. Mais nous pensons qu’il est intéressant de discuter les raisons de cet échec et ce qu’elles nous ont appris. Et nous n'avons pas abandonné l’idée de le faire fonctionner un jour... et ce projet progresse petit à petit.

Comme base de travail nous nous sommes servi du fichier source "/usr/share/arduino/hardware/arduino/bootloaders/atmega8/ATmegaBOOT.c" C'est un bootloader pour ATMega8. Nous avons réussi à le faire fonctionner mais le programme chargé ne démarre pas convenablement. Ainsi le programmateur avrdude croit discuter avec un Arduino. Dès que l'instruction SPM du bootloader est mise en commentaire, le dialogue entre notre ATMega8 et l'environnement Arduino se passe très bien. D'ailleurs ce même environnement ne sait même pas que rien n'a été écrit en mémoire programme...

Quant à la routine d'écriture en mémoire programme, nous avons déjà réussi à la faire fonctionner dans un autre contexte... mais pas correctement ici. Nous suspectons très fortement l'instruction ATMega8 SPM de ne pas avoir été implantée de manière suffisamment large pour un bootloader.

Documentation de l'instruction SPM
modifier

L'article AVR106: C functions for reading and writing to Flash memory précise que l'écriture dans la mémoire programme Flash se fait en trois étapes :

  • écriture d'une page de 32 mots dans une mémoire tampon
  • effacement de la page en mémoire Flash
  • transfert du tampon dans la mémoire flash.

Tout ceci est contrôlé par le registre associé à l'instruction SPM : SPMCR.

Nous avons d’autre part trouvé les informations complémentaires suivantes en lisant le code source du bootloader Arduino :

  • Une page pour l'ATMega8 est composée de 32 mots (64 octets).
  • Le registre Z est R31:R30 (auxquels on ajoute RAMPZ parfois) contient l'adresse où l’on veut écrire.
  • Le registre SPMCR est en adresse 0x37(0x57) est à implanter. Il sert à contrôler l'instruction SMP. Par exemple si l’on écrit 3 dedans avant SPM il effacera une page (pointée par Z), si l’on écrit 0x05 il écrira une page (pointée par Z) mais il faut le repasser en mode RWW avec 0x11 (dans SPMCR) auparavant, et en appelant SPM. S'il est mis à 1 cela met R1:R0 dans le registre temporaire de la Flash (pointé par le poids faible de Z).
  • R29:R28 contient l'adresse du buffer en RAM
  • R25:R24 contient la longueur à écrire

Donc, pour écrire un mot dans le buffer de page, charger le mot dans le registre R1:R0. S'arranger pour que le registre Z pointe sur le mot correct (bits b5 ... b1 [b0 est ignoré car on travaille en mots]) et mettre seulement le bit SPMEN du registre SPMCR et avant 4 cycles appler SPM.

 

La gestion des adresses mémoire programme nous a posé des problèmes suffisamment longtemps pour que nous décidions d'y attirer votre attention. Le compilateur C gère les mémoires avec des adresses d'octets. Votre matériel les gère en adresse de mots de 16 bits. Il y a donc un facteur 2 entre les deux. Ainsi une compilation avec l'option de placer le bootloader en 0x1C00 (vu par le compilateur) est associée à une initialisation matérielle en 0x0E00 qui est 0x1C00/2.

Nous n'avons pas réussi à faire fonctionner data2mem pour qu’il mette le bootloader à l'endroit voulu !!! Il était toujours en adresse 0x0000 malgré la bonne option de compilation. Probablement une option à chercher. Par contre nous avons réussi à le mettre à l'endroit voulu avec l'outil primitif make_mem !

Nouvelle instruction SPM (Juillet 2015)
modifier

Nous avons modifié l'instruction SPM comme suit :

Début d’un principe
Fin du principe


Cette implantation ne permet pas d’utiliser un bootloader tout fait car on y trouve des attentes sur le registre SPMCR qui ne fonctionne pas dans notre cas.

Le 7 juillet 2015, nous avons enfin réussi à faire fonctionner le programme :

où la seule partie intéressante est le main.

Ce programme écrit d'abord le mot "0xAA55" en mémoire programme dans des adresses consécutives, puis cherche ce même mot en affichant où il l'a trouvé. Excellent donc pour une vérification. Il montre que nous avons enfin réussi à faire cohabiter les instructions SPM et LPM. Cela peut sembler anodin, mais le fait que LPM travaille sur des adresses mémoires (programme) qui adressent des octets tandis que SPM adresse des mots nous a posé beaucoup de soucis. Désolé mais il ne faut pas grand chose parfois pour bloquer la mécanique.

Ce programme fonctionne normalement qu’il soit en adresse basse (comme un programme normal) ou en adresse haute (comme un bootloader). Pour info, la mise de ce programme en adresse haute se fait par :

export PATH=$PATH:/opt/Xilinx/14.5/ISE_DS/ISE/bin/lin64
avr-gcc -g -mmcu=atmega8 -Wall -Os -c ATmegaBOOT2.c
avr-gcc -g -mmcu=atmega8 -o ATmegaBOOT.elf -Wl,-Map,ATmegaBOOT.map,--section-start=.text=0x1C00 ATmegaBOOT2.o
avr-objcopy -R .eeprom -O ihex ATmegaBOOT.elf ATmegaBOOT.hex
./make_mem ATmegaBOOT.hex prog_mem_content.vhd
cp prog_mem_content.vhd ../../prog_mem_content.vhd #a adapter pour vous : prog_mem_content doit se trouver dans le projet vhdl
  • mise à true du generic BootLoader dans cpu_core.vhd pour qu'un reset l'emmène dans le bootloader
  • compilation totale du projet VHDL
  • chargement du fichier .bit

Ainsi, il est possible maintenant d'adapter le bootloader Arduino... Affaire à suivre....

Bootloader Arduino modifié

modifier

Puisque notre instruction SPM n’est pas conforme à celle de l'ATMega8 du commerce, il nous faut changer le bootloader. Par exemple le bootloader des premiers Arduino ATMega8 attend avant chaque instruction SPM que certains bits du registre de contrôle SPMCR passent à 1. Puisque nous ne gérons absolument pas ce registre, le code du bootloader est à changer... Voici donc une version modifiée du bootloader :

La complexité du code est due à la gestion du protocole STK500.

Le problème est que ce bootloader fonctionne presque en adresse basse 0x0000 mais moins bien si on le met là où il doit être, c’est-à-dire en adresse haute 0x1C00.

 

Les fonctionnements à ce jour sont les suivants :

  • en adresse haute il semble que le bootloader s'écrase lui-même puisque le protocole STK500 finit par planter. Du coup il finira par exécuter la première instruction de notre programme qui allume des LEDs (le fait qu’il s'écrase lui-même n'est que supposition)
  • en adresse basse, on le fait écrire avec un OFFSET de 0x0800 sur les adresses et on branche à l'adresse 0x400, alors le programme ne fonctionne que partiellement : il devrait faire clignoter 8 leds mais n'en fait clignoter qu'une seule ! On change la tempo et c’est observable. Il y a donc une corrélation entre ce que j’écris et ce que je vois, mais pas une corrélation totale comme on l'attends d'une programmation.
  • la différence entre les deux valeurs précédentes 0x0800 et 0x0400 est liée au fait qu’il y a des adresses d'octets et des adresses de mots à gérer. Nous avons fait des essais pour trouver les valeurs.

Ressource

modifier

Le fichier arduinoMega8.zip contient la version avec la nouvelle instruction SPM. Le fichier progmemcontent.vhd contient le bootloader (qui ne fonctionne pas correctement). Cette version a été réalisée avec l'option bootloader à false, ce qui veut dire que vous pouvez l’utiliser normalement (avec data2mem). Nous y avons aussi inséré la possibilité de réaliser une mise à 0 automatique d'un drapeau d'interruption, comme pour l'ATMega16 d'un autre chapitre. Rappelons que c’est la version la plus à jour.


L'obsession que nous avons à essayer de faire fonctionner le bootloader de cette section est liée à ce qu'une réussite permettrait d’utiliser notre FPGA dans une salle d'enseignement où l'environnement Arduino est installé. Il n'y aurait aucune modification à faire pour l'IDE Arduino. Mais bien sûr une modification matérielle nécessiterait l'ISE de Xilinx.

Comme notre objectif n’est pas encore atteint, nous donnons une variante dans la section suivante qui fonctionne, mais nécessite de changer l'IDE Arduino (plus exactement remplacer avrdude).

Protocole STK500v1 de l'Arduino détourné

modifier

En fait ce que nous voulons réaliser est de pouvoir utiliser l'environnement Arduino et le bouton de téléversement pour charger notre programme dans le FPGA. Un moyen évitant de faire fonctionner un bootloader, est d’utiliser data2mem au lieu de avrdude. Notre collègue Bastien Jacquot avait un problème avec des points communs : programmer un Arduino par le réseau. L’idée générale est la suivante :

  • ajouter une carte Arduino factice
  • remplacer avrdude par un script qui détecte s'il doit lancer avrdude ou s'il lance d'autres commandes

Voici comment on ajoute une carte Arduino.

  1. Chercher le fichier "/usr/share/arduino/hardware/arduino/boards.txt" et ajouter à la fin par exemple :
##############################################################
Xatmega16.name=Arduino Xilinx ATmega16
Xatmega16.upload.protocol=data2mem
Xatmega16.upload.speed=1200
Xatmega16.upload.maximum_size=16384
Xatmega16.upload.disable_flushing=true
Xatmega16.build.mcu=atmega16
Xatmega16.build.f_cpu=50000000L
Xatmega16.build.core=arduino
Xatmega16.build.variant=standard
  1. Renommer le fichier "/usr/share/arduino/hardware/tools/avrdude" (qui est un lien) en "/usr/share/arduino/hardware/tools/avrdude.arduino".
  2. Écrire le script et le mettre à la place de "/usr/share/arduino/hardware/tools/avrdude" :
#!/bin/bash
export PATH=$PATH:/opt/Xilinx/14.5/ISE_DS/ISE/bin/lin64
# Octobre 2013 (Bastien Jacquot)
params=$*

echo "Voici la liste des paramètres (un seul argument) : $params"

programmer=avrdude
for param in "$@"
do
   if [[ $param == -cdata2mem  ]]
   then
      programmer=data2mem
  elif [[ $param == -Uflash:w:* ]]
  then
      fichier=`echo $param| cut -d":" -f3`
  fi
done

if [[$programmer == avrdude]] 
then
        /usr/share/arduino/hardware/tools/avrdude.arduino $*
elif [[$programmer == data2mem]] 
then
      fichier=`echo $fichier| sed -e 's/.hex/.elf	/g'`
  data2mem -bm ~/memoryS6.bmm -bd $fichier -bt ~/atmega16.bit -o uh ~/atmega16
  echo "Transfert par ADEPT de $fichier"
  djtgcfg prog -d Nexys3 --index 0 --file ~/atmega16_rp.bit
fi

L'idée générale du script est la suivante :

  • repérer l’utilisation d'un programmateur data2mem
  • dans ce cas retrouver le fichier qui a l'extension .hex et le remplacer par le même fichier avec l'extension .elf
  • lancer data2mem
  • lancer ADEPT
  • autrement lancer avrdude normalement

Ce script nécessite les fichiers atmega16.bit et memmoryS6.bmm présents dans notre répertoire personnel. Il ne fonctionne que pour une carte nexys3.

La réalisation de de ce travail sous windows doit pouvoir se faire mais nous ne savons pas très bien comment.

Un autre bootloader qui reçoit directement des fichiers HEX

modifier

Voici comment Juergen Sauermann, le concepteur de l'ATMega8 a conçu son propre bootloader. Il a décidé de ne pas modifier le cœur pour ne pas faire de saut (matériel) en mémoire programme au moment du reset.

Notre cœur est forcément particulier par rapport au même composant du commerce : il n'a pas de fusibles par exemple.

 
Structure de notre bootloader et du programme chargé

Ce que Juergan a tenté de faire est un peu différent de la théorie présentée dans la section précédente à cause de l'absence de fusible. Une autre raison est que dans notre coreATMega8, nous ne sommes pas limité par l'instruction SPM contrairement à ce qui se passe dans l'ATMega8 (où elle fonctionne seulement lorsqu'elle est exécutée dans certaines zones de la mémoire programme).

Nous voyons poindre quelques différences avec la théorie de tout à l’heure, comme montrées en figure ci-contre :

  • le bootloader à gauche commence à l'adresse 0 par sa table de vecteurs d'interruption (en bleu clair)
  • sa partie donnée en mémoire programme (en gris) est gonflée artificiellement pour laisser de la place au futur programme (une autre façon de présenter les choses est de dire qu'un bootloader est petit en principe, mais que le notre est énorme !)
  • le programme commence lui aussi en adresse 0
  • l'écriture du programme en mémoire détruit la table de vecteur d'interruptions du bootloader et donc en particulier son vecteur RESET (partie droite de la figure où l’on montre les deux superposés)
  • en conséquence, il nous faut trouver un mécanisme pour que le programme soit capable d'appeler le bootloader car la figure de droite montre clairement que le bootloader n'a plus de point d'entrée après son écrasement par le programme : comment se brancher au programme bleu turquoise en fin de mémoire qui représente le bootloader ?

Une autre différence qui n'apparait pas sur le dessin est que nous enverrons par la RS232 des fichiers en format HEX (Intel) et non pas en binaire comme cela se fait traditionnellement. La raison est simple, il est plus facile d'opérer quelques vérifications sur un tel fichier, en particulier de tester si l’on est bien arrivé à la fin (du fichier), de tester s'il y a eu une erreur de transmission dans chacune des lignes... et puis on est prêt à utiliser quelques lignes de code supplémentaires pour notre bootloader en lui demandant de déchiffrer un fichier hex : si l’on manque de place on ira en chercher dans le FPGA.

 

Le bootloader donné dans la boîte déroulante ci-dessous ne fonctionne pas encore correctement pour le moment mais Juergen Sauermann nous affirme l'avoir fait fonctionner. Il est donc en cours de modification jusqu'à ce que cette bannière disparaisse.

Nous ne tenterons certainement pas de faire fonctionner ce bootloader sauf si nous n'arrivons pas à faire fonctionner celui de l'Arduino.

Adaptation au coreATMega8 de l'interface ISP

modifier

Comme toute interface, sa connaissance fine nécessite de connaître les deux parties qui échangent leurs informations :

  • partie logicielle qui tourne sur le PC
  • partie matérielle qui reçoit les données

La partie logicielle existe sous de nombreuses formes (AVRStudio, AVRDude, ...) nous n'aurons donc pas à la créer. La partie matérielle par contre est entièrement à réaliser : comment faire croire à AVRStudio ou AVRDude qu’il discute bien avec une interface ISP connectée à un ATMega8 ?

Il n'y a très peu de chances que la réponse à cette question soit documentée quelque part ! Pour y répondre il nous faudra certainement lire les codes sources d'un programmateur ! Ceci est remis à plus tard pour le moment.

Voir aussi

modifier

Programmation des mémoires chez Altera

modifier

La gestion des mémoires par Altera est extrêmement différente de celles de Xilinx. Il n'y a pas, à priori, d'équivalent direct à l'utilitaire data2mem. Nous avons utilisé le mot direct qui signifie ici, un utilitaire unique. On verra que deux utilitaires peuvent faire le job.

Les mémoires sont gérées avec les conventions LPM (library of parameterized modules). Pour la petite histoire, Xilinx était signataire de la norme LPM mais ne l'a jamais vraiment proposé dans ses packages... alors qu'Altera l'a toujours fait.

Voici un exemple qui utilise une RAM "lpm_ram_dq" :

LIBRARY lpm;
USE lpm.lpm_components.ALL;
--******** lignes supprimées ******
ma_ram : lpm_ram_dq 
      GENERIC MAP(LPM_WIDTH => 8,
      --- LPM_TYPE: STRING := L_RAM_DQ,
      LPM_WIDTHAD => 10,
      --LPM_NUMWORDS: STRING := UNUSED,
      LPM_FILE => "ma_ram.mif"
      --LPM_INDATA: STRING := REGISTERED;
      --LPM_ADDRESS_CONTROL => "UNREGISTERED",
      --LPM_OUTDATA => "UNREGISTERED"
      )
      port map (
          data=>"11111111" ,
          address(9 downto 3)=> "0000000", 
          address(2 downto 0) => switch,
          inclock=>clk,
          outclock=>clk,
          we =>'0',
          q=> s_color_led
      );
--********* et plein d'autres choses encore

Le point essentiel est de remarquer que l’on a associé une mémoire à un fichier "ma_ram.mif" qui donne le contenu de la mémoire. A priori deux formats de fichiers sont possibles :

  • MIF (Memory Initiation File)
  • HEX bien connu des programmeurs de microcontroleurs

Le problème est, que par défaut, le changement du fichier MIF ou HEX du projet suivi d'une recompilation recompile l’ensemble du projet, ce qui prend un certain temps.

Il nous faut donc trouver des solutions pour contourner cela.

Dans l'environnement intégré Quartus

modifier

Pour la mise à jour du contenu de la mémoire seulement, sans passer par un temps de recompilation complet de l’application (donc plus ou moins équivalent à data2mem), il faut valider l'option "use smart compilation". Pour y accéder : menu assignements => settings => compilation process settings => puis cocher l'option . ne pas oublier de faire "apply" avant de quitter la fenêtre !

Ainsi, changer son fichier .mif et lancer une compilation classique qui devrait être plus rapide (environ 18 secondes là où on avait 1 min 15 s pour un premier essai sans processeur)

En ligne de commande

modifier

Si testvga est un nom de projet, on peut mettre à jour les mémoires par les deux commandes :

/opt/altera/15.0/quartus/bin/quartus_cdb --update_mif testvga
/opt/altera/15.0/quartus/bin/quartus_asm testvga

Il ne vous reste alors plus qu’à utiliser USBBLASTER pour programmer votre FPGA.


Projet CQPIC (PIC 16F)

modifier

Utiliser data2mem avec CQPIC

modifier

Le projet CQPIC est traité dans le chapitre Embarquer un PIC 16F84.

Comme les sections précédentes le montrent il nous faut un fichier au format elf pour travailler avec data2mem. Ainsi il nous faudra changer de compilateur. Dans cette section, nous allons donc travailler avec SDCC un compilateur libre qui supporte maintenant les PICs 16Fxxx (voir Bico64 pour un exemple utilisant sdcc).

Utiliser sdcc est une mauvaise piste

modifier

Pour le moment voir premiers pas avec sdcc.

Une lecture de la documentation de sdcc nous a appris que le format objet utilisé par sdcc n’est pas ELF (Executable and Linking Format) mais (en) COFF (Common Object File Format) ! L'utilitaire data2mem n’est pas capable de gérer le format COFF. Il peut cependant gérer le format mem qui doit être aisé à réaliser à partir d'un fichier HEX. Ce problème est en fait résolu en section suivante.

Utilisation générale de data2mem

modifier

Nous venons de découvrir en lisant (en) Using the AX8 (AVR clone) FPGA core from opencores qu'un utilitaire convertit un fichier hex (Intel) en fichier mem. Visitez la page (en) SRecord 1.60 Files for Download pour le télécharger.

Utiliser le JTAG pour programmer

modifier

Liens externes

modifier

Déboguer un programme dans un soft processeur

modifier

La difficulté pour déboguer un programme dans un soft processeur est qu'en général la technique classique consistant à ajouter des "printf" ne fonctionne plus car, en général, vous ne disposez plus d'un écran. Il est facile cependant de remplacer un écran par un hyperterminal car cette façon de faire ne nécessite qu'une liaison série. Faire fonctionner une liaison série est en général relativement facile dans un FPGA.

Déboguer un programme avec le cœur ATMega8

modifier

Nous avons déjà eu l’occasion de signaler que ce cœur possède une liaison série en standard qui fonctionne à 38400 bauds. Elle a été utilisée par exemple dans le projet Pong et téléphone portable. Nous rappelons ici cependant les fonctions C de base pour communiquer par cette liaison et décrirons plus tard des exemples d'utilisation.

//************************************************************************
// function uart_init()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 38400,8,n,2 hard coded : transmission and reception
//************************************************************************
void usart_init(void) {
  UCSRB = (1<<TXEN)|((1<<RXEN)); // transmission et reception
}
 
//************************************************************************
// function uart_send()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 38400,8,n,2 hard coded
//************************************************************************
void usart_send(unsigned char ch){
  while(!(UCSRA & (1<<UDRE)));
  UDR = ch;
}
//************************************************************************
// function uart_receive() 
// purpose: read character in second rs232 PORT 
// arguments: 
// corresponding character 
// return: non-blocking sub return 0 if no data present else return char 
// note: 38400,8,n,2 hard coded, non-blocking sub return 0 if no data present 
//************************************************************************
char usart_receive(void){ 
  if (UCSRA & (1<<RXC)) //si Data Present en réception 
    return UDR 
  else return 0; 
}

Une version bloquante de "usart-receive" peut facilement être réalisée à partir de ce code. Cette version ne retournerait plus 0 s'il n'y a rien de reçu mais attendrait tout simplement que quelque chose arrive !

//************************************************************************
// function uart_receive()
// purpose: read character in second rs232 PORT
// arguments:
// corresponding character
// return: blocking sub return char
// note: 38400,8,n,2 hard coded, non-blocking sub return 0 if no data present
// initialisation uart prealable requise 
//************************************************************************
char usart_receive(void){
  while (!(UCSRA & (1<<RXC))); //attente tant que Data Present en réception
    return UDR;
}

Ces fonctions de bases permettent de construire des fonctions un peu plus évoluées comme :

//************************************************************************
// function usart_gets()
// purpose: gets characters in rs232 PORT
// arguments:
// corresponding string where characters are put
// note: 38400,8,n,2 hard coded : transmission 
// initialisation uart prealable requise
//************************************************************************
void usart_gets(char str[]) {
  uint8_t i=0;
  do {
    str[i]=usart_receive();
    i++;
  } while(str[i-1]!=0x0D); // carriage return ?
  str[i-1]=0;//end of string
}

//************************************************************************
// function usart_puts()
// purpose: puts characters in first rs232 PORT
// arguments:
// corresponding string (with O as last character)
// return:
// note: 38400,8,n,2 hard coded : transmission
// initialisation uart prealable requise 
//************************************************************************
void usart_puts(char str[]){
  uint8_t i=0;
  do {
    usart_send(str[i]);
    i++;
  } while(str[i]!=0);
}

//************************************************************************
// function usart_puts_hexa()
// purpose: puts number in hexadecimel in first rs232 PORT
// arguments:
// corresponding number
// return:
// note: 38400,8,n,2 hard coded : transmission
// initialisation uart prealable requise 
// only for 16-bit numbers and then Q3.13 numbers
//************************************************************************
void usart_puts_hexa(int nbQ3_13){
  int8_t i=0,digit=0;
  char char_digit;
  usart_send('0');usart_send('X');
  for (i=12;i>-1;i-=4) {// only four digits
     digit = (nbQ3_13 >> i) & 0x0F;
     char_digit=digit+0x30;
     if (char_digit>0x39) char_digit += 7;
     usart_send(char_digit);
  }  
}

Utiliser un débogueur pour faire du pas à pas

modifier

Les "printf" de la section précédente sont faciles à mettre en œuvre mais ne permettent pas de réaliser du pas à pas. Pour cela, il faut dialoguer avec un débogueur et c’est ce problème que nous désirons évoquer dans cette section.

Voir aussi

modifier

Vous pouvez lire l’article de James W. Grenning dans Dr.Dobb's (Juillet 2013) ainsi que la librairie C associée : Unity.

Déboguer matériellement un soft processeur

modifier

Déboguer un soft processeur est particulièrement délicat s'il ne possède pas son interface JTAG correspondante. C'est, dans ce cas précis, à vous de trouver des techniques de mise au point.

À la différence de ce qui a été examiné dans la partie sur la mise au point du logiciel, nous allons traiter dans cette section de la mise au point nécessitant l'ajout d'une partie matérielle.

Mise au point du bootloader dans l'ATMega8

modifier

Lors de nos essais de mise au point du bootloader Arduino dans notre cœur, nous nous sommes trouvés confronté à un problème qui nous a été difficile de classer : matériel ou logiciel ? À ce jour le problème n’est pas résolu mais nous avons décidé de sortir le compteur programme et de ralentir fortement l'horloge (division par 2^23) et nous présentons ce travail qui pourra certainement servir dans un autre contexte.

Pour votre information, nous avons toujours utilisé le compteur programme affiché sur des afficheurs sept segments pour nous rendre compte si le cœur fonctionnait ou pas. Ceci nous semble donc une technique à conseiller.

Sortir le compteur programme pour deboguer

modifier

Pour résoudre nos problèmes nous avons été amené à ralentir très fortement le cœur et sortir le compteur programme. Ceci se fait très rapidement avec le cœur dont nous disposons :

  • modifier l'entité de notre cœur ATMega8 :
-- Carte utilisée : Nexys 3 (horloge à {{unité|100|MHz}})
entity atmega8 is
    port (  I_CLK_100   : in  std_logic; -- instead of I_CLK_50
            I_PINB    : in  std_logic_vector(7 downto 0);
            I_RX        : in  std_logic;		
            I_CLR        : in  std_logic;
            Q_PORTD : out std_logic_vector(7 downto 0); --digit selection	
            Q_PORTB : out std_logic_vector(7 downto 0);
            Q_PORTC      : out std_logic_vector(7 downto 0);
--> added 2014/07/25 allows a PC debugging 
            Q_PC  : out std_logic_vector(15 downto 0);
--<				
            Q_TX        : out std_logic);
end atmega8;

où vous voyez apparaître Q_PC sur 16 bits qui est le compteur programme.

  • relier cette nouvelle sortie au compteur programme. Cela se fait dans l'architecture correspondante dans le câblage du composant CPU_core où Q_PC qui était relié à open est maintenant à relier à Q_PC
  • enfouir cet ancien composant dans un nouveau qui permet le ralentissement et l’affichage du compteur programme sur les quatre afficheurs de la carte.

Cette technique nous a permis de trouver que :

  • le RESET matériel à l'adresse 0x1C00(0x0E00) pour l'Arduino fonctionne correctement (alors que c’est la première chose que nous avons suspecté)
  • que c’est data2mem qui n’est pas capable de mettre le programme bootloader en adresse 0x1C00(0x0E00)

Pas mal non pour un si petit bout de programme !

Plus loin encore

modifier

Essayer de mettre en œuvre l'interface debug de chez Opencore qui semble bien documentée.

Voir aussi

modifier