Logiciels Libres et Systèmes Embarqués


2.4. Assistant de création

Il faut préciser à l'assistant de création le modèle de la carte (figure 2.5), pour que celui-ci propose les bons périphériques et utilise les bons paramètres. Deux types de processeurs sont proposés (figure 2.6), il faut choisir bien évidemment le PowerPC 405. Ensuite, il faut préciser la fréquence du bus et du processeur, ainsi que l'interface de débogage (figure 2.7). Puis, vient la sélection et la configuration des périphériques externes[5]. Dans un premier temps, il faut un UARTLite, c'est à dire un contrôleur série de Xilinx, pour pouvoir se connecter au système par RS232 (figure 2.8). Puis il faut choisir le type de mémoire (SDRAM, SRAM, Flash, CompactFlash) (figures 2.8 et 2.9), mais il est impossible d'en choisir plusieurs en même temps car celles-ci utilisent un bus partagé de mémoire externe. L'assistant n'étant pas capable de multiplexer ces signaux, nous le ferons nous-même par la suite. Pour terminer avec les périphériques externes, il est intéressant d'utiliser aussi un XEmac, c'est à dire un contrôleur réseaux Ethernet de Xilinx (figure 2.10). Il faut ensuite sélectionner et configurer les périphériques internes (figure 2.11). Pour cet exemple, le choix se porte simplement sur un contrôleur de BRAM qui sera mappé à l'adresse de démarrage du processeur[6], c'est à dire 0xFFFFFFFC. Enfin, on doit choisir les entrées/sorties d'affichage pour les logiciels d'exemples (figure 2.12). Finalement, l'assistant affiche le résumé des choix effectués (figure 2.13).

Figure 2.5. Choix de la carte FPGA utilisée

Choix de la carte FPGA utilisée

Figure 2.6. Choix du processeur utilisé

Choix du processeur utilisé

Figure 2.7. Choix des fréquences, de l'interface de débogage

Choix des fréquences, de l'interface de débogage

Figure 2.8. Choix des périphériques externes

Choix des périphériques externes

Figure 2.9. Choix des périphériques externes (mémoires)

Choix des périphériques externes (mémoires)

Figure 2.10. Choix des périphériques externes (Ethernet)

Choix des périphériques externes (Ethernet)

Figure 2.11. Choix des périphériques internes

Choix des périphériques internes

Figure 2.12. Choix des applications de tests

Choix des applications de tests

Figure 2.13. Récapitulatif de ce système

Récapitulatif de ce système

2.4.1. Modifications EDK de l'architecture

Nous venons de créer une architecture simple comportant uniquement de la SDRAM en tant que mémoire. Il est parfois souhaitable d'utiliser de la Flash ou la CompactFlash pour stocker de manière persistante[7] des données ou des programmes. Toutes les modifications qui seront effectuées figureront dans le fichier system.mhs [9], qui contient la description de l'architecture.

Pour que les contrôleurs de mémoire persistante fonctionnent, il faut dans un premier temps enlever les lignes désactivant leur ChipSelect :

 PORT emc_disable_flash  = net_vcc,   DIR = OUTPUT
 PORT emc_disable_sysace = net_vcc,   DIR = OUTPUT

Il faut ajouter un autre bus OPB où seront raccordées ces deux mémoires, le support d'Avnet m'ayant conseillé d'appliquer cette méthode pour éviter les conflit d'accès. Il suffit donc d'ajouter les lignes suivantes :

BEGIN opb_v20
 PARAMETER INSTANCE         = opb_slowmem
 PARAMETER HW_VER           = 1.10.c
 PARAMETER C_EXT_RESET_HIGH = 1
 PORT SYS_Rst               = sys_bus_reset
 PORT OPB_Clk               = half_sys_clk_s
END

Maintenant que ce bus OPB est créé, il faut rajouter un pont entre celui-ci et le bus PLB, qui est le seul à avoir un accès direct avec le processeur. Nous ne nous occupons pas des plages mémoires pour le moment :

BEGIN plb2opb_bridge
 PARAMETER INSTANCE        = plb2opb_slowmem
 PARAMETER HW_VER          = 1.01.a
 PARAMETER C_DCR_INTFCE    = 0
 PARAMETER C_NUM_ADDR_RNG  = 1
 PARAMETER C_RNG0_BASEADDR = 0x40000000
 PARAMETER C_RNG0_HIGHADDR = 0x7fffffff
 BUS_INTERFACE SPLB        = plb
 BUS_INTERFACE MOPB        = opb_slowmem
 PORT PLB_Clk              = sys_clk_s
 PORT OPB_Clk              = half_sys_clk_s
END

Comme ce nouveau bus est cadencé à une fréquence plus faible (50 MHz), il faut rajouter une "Digital Clock Manager" (DCM) :

BEGIN dcm_module
 PARAMETER INSTANCE         = dcm_slowmem
 PARAMETER HW_VER           = 1.00.a
 PARAMETER C_CLKDV_BUF      = TRUE
 PARAMETER C_CLKDV_DIVIDE   = 2.000000
 PARAMETER C_CLKIN_PERIOD   = 10.000000
 PARAMETER C_CLK_FEEDBACK   = 1X
 PARAMETER C_EXT_RESET_HIGH = 1
 PORT CLKIN                 = dcm_clk_s
 PORT CLKDV                 = half_sys_clk_s
 PORT CLKFB                 = half_sys_clk_s
 PORT RST                   = net_gnd
END

Nous pouvons à présent rajouter le contrôleur Flash :

BEGIN opb_emc
 PARAMETER INSTANCE            = FLASH_4Mx32
 PARAMETER HW_VER              = 2.00.a
 PARAMETER C_OPB_CLK_PERIOD_PS = 20000
 PARAMETER C_NUM_BANKS_MEM     = 1
 PARAMETER C_TCEDV_PS_MEM_0    = 130000
 PARAMETER C_TWC_PS_MEM_0      = 55000
 PARAMETER C_TAVDV_PS_MEM_0    = 250000
 PARAMETER C_TWP_PS_MEM_0      = 55000
 PARAMETER C_THZCE_PS_MEM_0    = 35000
 PARAMETER C_TLZWE_PS_MEM_0    = 35000
 PARAMETER C_MEM0_BASEADDR     = 0x20000000
 PARAMETER C_MEM0_HIGHADDR     = 0x20ffffff
 BUS_INTERFACE SOPB            = opb_slowmem
 PORT OPB_Clk                  = half_sys_clk_s
 PORT Mem_A                    = FLASH_A
 PORT Mem_DQ                   = FLASH_DQ
 PORT Mem_WEN                  = FLASH_WEN
 PORT Mem_OEN                  = FLASH_OEN
 PORT Mem_CEN                  = FLASH_CEN
 PORT Mem_RPN                  = FLASH_RPN
END

Il en va de même pour le contrôleur CompactFlash (SystemACE) :

BEGIN opb_sysace
 PARAMETER INSTANCE    = SysACE_CompactFlash
 PARAMETER HW_VER      = 1.00.b
 PARAMETER C_MEM_WIDTH = 8
 PARAMETER C_BASEADDR  = 0x41800000
 PARAMETER C_HIGHADDR  = 0x4180ffff
 BUS_INTERFACE SOPB    = opb_slowmem
 PORT OPB_Clk          = half_sys_clk_s
 PORT SysACE_IRQ       = SysACE_IRQ
 PORT SysACE_CLK       = SysACE_CLK
 PORT SysACE_MPA       = SysACE_MPA
 PORT SysACE_MPD       = SysACE_MPD
 PORT SysACE_CEN       = SysACE_CEN
 PORT SysACE_OEN       = SysACE_OEN
 PORT SysACE_WEN       = SysACE_WEN
 PORT SysACE_MPIRQ     = SysACE_MPIRQ
END

Enfin, il ne reste plus qu'à rajouter les ports externes, c'est à dire ceux qui vont interagir avec des éléments externes à la puce FPGA :

 PORT FLASH_DQ  = FLASH_DQ,  DIR = INOUT,  VEC = [0:31]
 PORT FLASH_A   = FLASH_A,   DIR = OUTPUT, VEC = [0:31]
 PORT FLASH_WEN = FLASH_WEN, DIR = OUTPUT
 PORT FLASH_OEN = FLASH_OEN, DIR = OUTPUT, VEC = [0:0]
 PORT FLASH_RPN = FLASH_RPN, DIR = OUTPUT
 PORT FLASH_CEN = FLASH_CEN, DIR = OUTPUT, VEC = [0:0]

 PORT SysACE_CLK   = SysACE_CLK,   DIR = INPUT
 PORT SysACE_MPD   = SysACE_MPD,   DIR = INOUT,  VEC = [0:7]
 PORT SysACE_MPA   = SysACE_MPA,   DIR = OUTPUT, VEC = [0:6]
 PORT SysACE_WEN   = SysACE_WEN,   DIR = OUTPUT
 PORT SysACE_OEN   = SysACE_OEN,   DIR = OUTPUT
 PORT SysACE_CEN   = SysACE_CEN,   DIR = OUTPUT
 PORT SysACE_MPIRQ = SysACE_MPIRQ, DIR = INPUT

On aurait pu ajouter les périphériques en passant par l'interface graphique d'EDK. La méthode employée ici est équivalente, mais je ne voulais pas noyer le lecteur sous les captures d'écrans. Les captures d'écran de la section suivante donne un apperçu suffisant de l'interface d'EDK.

2.4.2. Configuration du logiciel

Maintenant que ce système est complet, on doit définir quels sont les pilotes à utiliser pour la création d'un logiciel avec EDK (figure 2.14). L'outil de configuration automatique d'EDK va être ensuite utilisé pour déterminer les plages d'adresses des périphériques (figure 2.15). Enfin, ce projet est exporté vers la suite d'outils ISE afin de modifier le code VHDL pour le multiplexage du bus partagé de mémoire (figure 2.16).

Figure 2.14. Choix des pilotes

Choix des pilotes

Figure 2.15. Choix des plages d'adresses

Choix des plages d'adresses

Figure 2.16. Exportation vers ISE

Exportation vers ISE

2.4.3. Modifications VHDL de l'architecture

Maintenant que nous avons créé une architecture complexe avec EDK, nous devons multiplexer ce fameux bus partagé de mémoire. Toutes les opérations figureront dans le fichier system_top.hdl. Lors de l'exportation vers ISE, EDK crée une entité system_stub représentant le système vu de l'extérieur. Cependant, il ne la génère pas correctement puisqu'il n'a pas connaissance du bus partagé. On se retrouve alors avec un système dont les mémoires ont des ports d'entrées/sorties individuels. Il faut alors remplacer cette entité par la suivante :

entity system_stub is
  port (
    SMB_DQ   : inout std_logic_vector(0 to 31);
    SMB_Addr : out   std_logic_vector(0 to 23);
    SMB_WEn  : out   std_logic;
    SMB_OEn  : out   std_logic;
    SMB_RPn  : out   std_logic;
    SMB_BSn  : out   std_logic_vector(0 to 3);

    SDRAM_CASn : out std_logic;
    SDRAM_RASn : out std_logic;
    SDRAM_Clk  : out std_logic;

    SysACE_Clk   : in std_logic;
    SysACE_MPIRQ : in std_logic;

    Flash_CSn  : out std_logic;
    SDRAM_CSn  : out std_logic;
    SRAM_CSn   : out std_logic;
    Buffer_CSn : out std_logic;
    SysACE_CSn : out std_logic;

-- il manque les ports de l'EMAC, de l'UART, de l'horloge et du reset
  );
end system_stub;

Les ports d'entrées/sorties des mémoires sont alors des signaux internes que l'on multiplexera sur les vrais ports du bus partagé :

architecture STRUCTURE of system_stub is
  component system is
    port (
-- tous les ports externes du système EDK
    );
  end component;

  signal SDRAM_BankAddr      : std_logic_vector(0 to 1);
  signal SDRAM_Addr          : std_logic_vector(0 to 11);
  signal SDRAM_WEn           : std_logic;
  signal SDRAM_CSn_internal  : std_logic;

  signal SDRAM_DQ_O          : std_logic_vector(0 to 31);
  signal SDRAM_DQ_T          : std_logic_vector(0 to 31);

  signal Flash_Addr          : std_logic_vector(0 to 31);
  signal Flash_CSn_internal  : std_logic_vector(0 to 0);
  signal Flash_WEn           : std_logic;
  signal Flash_OEn           : std_logic_vector(0 to 0);

  signal Flash_DQ_O          : std_logic_vector(0 to 31);
  signal Flash_DQ_T          : std_logic_vector(0 to 31);

  signal SysACE_MPA          : std_logic_vector(0 to 6);
  signal SysACE_WEN          : std_logic;
  signal SysACE_CSn_internal : std_logic;
  signal SysACE_OEn          : std_logic_vector;

  signal SysACE_MPD_O        : std_logic_vector(0 to 7);
  signal SysACE_MPD_T        : std_logic_vector(0 to 7);

-- il manque les signaux des buffers de l'EMAC

Ensuite, on doit faire la correspondance entre le système du point de vue EDK et les signaux internes :

  system_top : system
    port map (
      SDRAM_BankAddr => SDRAM_BankAddr,
      SDRAM_Addr     => SDRAM_Addr,
      SDRAM_DQM      => SMB_BSn,
      SDRAM_CASn     => SDRAM_CASn,
      SDRAM_RASn     => SDRAM_RASn,
      SDRAM_Clk      => SDRAM_Clk,
      SDRAM_WEn      => SDRAM_WEn,
      SDRAM_CSn      => open,           --SDRAM_CSn_internal

      emc_disable_sram   => SRAM_CSn,
      emc_disable_buffer => Buffer_CSn,

      FLASH_A   => FLASH_Addr,
      FLASH_WEN => FLASH_WEn,
      FLASH_OEN => FLASH_OEn(0 to 0),
      FLASH_RPN => SMB_RPn,
      FLASH_CEN => FLASH_CSn_internal(0 to 0),

      SysACE_CLK   => SysACE_CLK,
      SysACE_MPA   => SysACE_MPA,
      SysACE_WEN   => SysACE_WEN,
      SysACE_OEN   => SysACE_OEn,       
      SysACE_CEN   => SysACE_CSn_internal,
      SysACE_MPIRQ => SysACE_MPIRQ,

      SDRAM_DQ_I => SMB_DQ,
      SDRAM_DQ_O => SDRAM_DQ_O,
      SDRAM_DQ_T => SDRAM_DQ_T,

      FLASH_DQ_I => SMB_DQ,
      FLASH_DQ_O => FLASH_DQ_O,
      FLASH_DQ_T => FLASH_DQ_T,

      SysACE_MPD_I => SMB_DQ,
      SysACE_MPD_O => SysACE_MPD_O,
      SysACE_MPD_T => SysACE_MPD_T

-- puis viennent les ports de l'EMAC, de l'UART, de l'horloge et du reset
      );

Il ne reste alors plus qu'à rajouter la logique de multiplexage :

SysACE_CSn <= SysACE_CSn_internal;
Flash_CSn <= Flash_CSn_internal(0) when SysACE_CSn_internal = '1'
             else '1';
SDRAM_CSn_internal <= not(SysACE_CSn_internal) or
                      not(Flash_CSn_internal(0));

SDRAM_CSn <= SDRAM_CSn_internal;

SMB_WEn <= SysACE_WEn and SDRAM_WEn and Flash_WEn;

SMB_Addr <= "0000000" & SDRAM_BankAddr & "0" & SDRAM_Addr & "00"
                                            when SDRAM_CSn_internal = '0'
            else "00" & Flash_Addr(8 to 29) when Flash_CSn_internal = "0"
            else "00000000000000000" & SysACE_MPA;

SMB_DQ <= "000000000000000000000000" & SysACE_MPD_O 
                                            when (SysACE_MPD_T(0) = '0')
          else Flash_DQ_O when (Flash_DQ_T(0) = '0')
          else SDRAM_DQ_O when (SDRAM_DQ_T(0) = '0')
          else "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";

SMB_OEn <= Flash_OEN(0) when Flash_CSn_internal(0) = '0'
           else SysACE_OEn;

EMAC_Mii_clk <= EMAC_Mii_clk_O when (EMAC_Mii_clk_T = '0')
                else 'Z';

EMAC_Mii_data <= EMAC_Mii_data_O when (EMAC_Mii_data_T = '0')
                 else 'Z';

Si on regarde avec attention la manière dont le multiplexage est réalisé, on se rend compte que la SDRAM est la moins prioritaire. En d'autres termes, en cas d'accès simultané à une mémoire persistante et à la SDRAM, c'est l'accès à la mémoire persistante qui sera effectif. Cela peut paraître sous-optimal puisque les accès à la SDRAM prennent moins de temps et sont plus importants (ils alimentent le processeur en instructions). Il est impossible de faire autrement car le ChipSelect du contrôleur SDRAM provenant du système EDK est toujours activé (câblé sur GND, actif niveau bas), on ne sait donc jamais s'il y a un réel accès ou pas. En conclusion, pour que l'on puisse donner la main à la SDRAM, il faut qu'aucune autre des mémoires ne fassent d'accès. On pourrait se dire que le logiciel ne peut pas écrire en même temps dans les mémoires, mais il faut faire très attention car les contrôleurs peuvent très bien être asynchrones (en utilisant des tampons).

Le multiplexage réalisé est illustré par la table de vérité suivante :

Tableau 2.1. Multiplexage des signaux ChipSelect

 Flash_CSn_internal
01
SysACE_CSn_internal0SysACE_CSnSysACE_CSn
1Flash_CSnSDRAM_CSn

Finalement, il ne reste qu'à remplir le fichier de concordances entre les ports et les broches, à synthétiser et à importer de nouveau le projet sous EDK.

Figure 2.17. Schéma simplifié de la logique de multiplexage

Schéma simplifié de la logique de multiplexage



[5] c'est à dire qui interagissent avec des éléments externes au FPGA.

[6] "vector reset" en anglais.

[7] qui conserve les données lors d'un arrêt.