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).
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.
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).
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 | |||
0 | 1 | ||
SysACE_CSn_internal | 0 | SysACE_CSn | SysACE_CSn |
1 | Flash_CSn | SDRAM_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.