Zynq + Petalinux
Transcription
Zynq + Petalinux
Conservatoire National des Arts et Métiers Zynq + Petalinux Vivado 2015.1, Petalinux 2015.2.1 Ubuntu 14.04 LTS 64 bits ZC702 rev 1.1 C.ALEXANDRE, lundi 14 novembre 2016 1 2 3 Les GPIO sous Linux sans IRQ .......................................................................................... 1 1.1 Introduction ................................................................................................................. 1 1.2 Le design de test vivado .............................................................................................. 2 1.3 L’application standalone.............................................................................................. 8 1.4 Création du projet petalinux ...................................................................................... 11 1.5 Test QEMU puis boot ZC702 .................................................................................... 23 1.6 Accès aux MIO et AXI GPIO via sysfs ..................................................................... 29 1.7 Développement d’une application sysfs .................................................................... 31 1.8 Insertion de l’application sysfs dans la distribution petalinux .................................. 39 1.9 Les drivers gpio-leds et gpio-keys ............................................................................. 42 1.10 Accès aux registres via peek et poke ..................................................................... 55 1.11 Développement d’une application utilisant /dev/mem........................................... 59 1.12 Développement d’une application utilisant UIO framework ................................. 62 Les GPIO sous Linux avec IRQ........................................................................................ 71 2.1 Le design de test Vivado ........................................................................................... 71 2.2 Les applications standalone ....................................................................................... 73 2.3 Création du projet Petalinux avec UIO...................................................................... 85 2.4 Développement des applications UIO framework ..................................................... 87 Acquisition avec IP AXI Lite et transfert EMIO .............................................................. 97 3.1 Introduction ............................................................................................................... 97 3.2 Création du composant IP_TEST .............................................................................. 98 3.3 Création du design de test ZC702_ACQ ................................................................. 126 3.4 En cas de modification/erreur sur le composant IP_TEST...................................... 132 3.5 L’application standalone.......................................................................................... 138 3.6 Création du projet petalinux .................................................................................... 141 3.7 Développement d’une application Linux de test ..................................................... 145 3.8 Mesure du temps d’éxécution d’une fonction ......................................................... 150 i 3.9 3.10 Performances ........................................................................................................... 151 Développement d’une application socket client/serveur ..................................... 152 ii 1 LES GPIO SOUS LINUX SANS IRQ 1.1 INTRODUCTION Lors de l’installation des différents outils Xilinx (Vivado, SDK et Petalinux), il a fallu ajouter à la fin du fichier .bashrc des commandes pour lancer des scripts de configuration : Ce fichier .bashrc est lancé automatiquement lors de l’ouverture d’un Terminal . On voit alors s’afficher le résultat du lancement des différents scripts : Le lancement d’un terminal est donc obligatoire avant de pouvoir utiliser les outils de développement de Petalinux. 1 1.2 LE DESIGN DE TEST VIVADO Dans ce design, nous allons tester les MIO, EMIO et AXI GPIO sans utiliser les interruptions. Nous allons utiliser les I/O suivantes : MIO10 DS23 MIO12 SW14 SW15-1 et SW15-2 sont MIO14 SW13 branchés en parallèle sur EMIO54 SW5 EMIO55 SW7 EMIO56 DS15 EMIO57 DS16 EMIO58 DS17 EMIO59 DS18 Attention : les switches SW14 et SW13 AXI GPIO0 SW12-1 AXI GPIO1 SW12-2 AXI GPIO2 DS22 AXI GPIO3 DS21 AXI GPIO4 DS20 AXI GPIO5 DS19 • Créez un projet Vivado 2015.1 en VHDL appelé zc702_test_gpio, pour une ZC702 (dernière version). • Créez un block Design appelé system et insérez dedans un Zynq7. • Puis cliquez sur « Run Block automation ». • Double cliquez ensuite sur le composant et ajoutez 6 EMIO : 2 • Réglez l’horloge clk0 à 100 MHz : 3 • Ajoutez ensuite un AXI GPIO puis double cliquez dessus et paramétrez-le : • Puis faites 4 « Make External » sur les 2 GPIO et les 2 EMIO : 4 • Cliquez ensuite sur « Run Connection Automation » pour obtenir finalement : Vous devez enfin : • valider le block design. • sauver et fermer le block design. • générer les produits de sortie (output products). • créer le wrapper VHDL (en mode copie). Le Wrapper doit être modifié de la manière suivante : library IEEE; use IEEE.STD_LOGIC_1164.ALL; library UNISIM; use UNISIM.VCOMPONENTS.ALL; entity system_wrapper is port ( DDR_addr : inout STD_LOGIC_VECTOR ( 14 downto 0 ); DDR_ba : inout STD_LOGIC_VECTOR ( 2 downto 0 ); DDR_cas_n : inout STD_LOGIC; DDR_ck_n : inout STD_LOGIC; DDR_ck_p : inout STD_LOGIC; DDR_cke : inout STD_LOGIC; DDR_cs_n : inout STD_LOGIC; DDR_dm : inout STD_LOGIC_VECTOR ( 3 downto 0 ); DDR_dq : inout STD_LOGIC_VECTOR ( 31 downto 0 ); DDR_dqs_n : inout STD_LOGIC_VECTOR ( 3 downto 0 ); DDR_dqs_p : inout STD_LOGIC_VECTOR ( 3 downto 0 ); DDR_odt : inout STD_LOGIC; DDR_ras_n : inout STD_LOGIC; DDR_reset_n : inout STD_LOGIC; DDR_we_n : inout STD_LOGIC; FIXED_IO_ddr_vrn : inout STD_LOGIC; FIXED_IO_ddr_vrp : inout STD_LOGIC; FIXED_IO_mio : inout STD_LOGIC_VECTOR ( 53 downto 0 ); 5 FIXED_IO_ps_clk : inout STD_LOGIC; FIXED_IO_ps_porb : inout STD_LOGIC; FIXED_IO_ps_srstb : inout STD_LOGIC; sw_emio : in STD_LOGIC_VECTOR ( 1 downto 0 ); leds_emio : out STD_LOGIC_VECTOR ( 3 downto 0 ); leds_gpio : out STD_LOGIC_VECTOR ( 3 downto 0 ); sw_gpio : in STD_LOGIC_VECTOR ( 1 downto 0 ) ); end system_wrapper; architecture STRUCTURE of system_wrapper is component system is port ( DDR_cas_n : inout STD_LOGIC; DDR_cke : inout STD_LOGIC; DDR_ck_n : inout STD_LOGIC; DDR_ck_p : inout STD_LOGIC; DDR_cs_n : inout STD_LOGIC; DDR_reset_n : inout STD_LOGIC; DDR_odt : inout STD_LOGIC; DDR_ras_n : inout STD_LOGIC; DDR_we_n : inout STD_LOGIC; DDR_ba : inout STD_LOGIC_VECTOR ( 2 downto 0 ); DDR_addr : inout STD_LOGIC_VECTOR ( 14 downto 0 ); DDR_dm : inout STD_LOGIC_VECTOR ( 3 downto 0 ); DDR_dq : inout STD_LOGIC_VECTOR ( 31 downto 0 ); DDR_dqs_n : inout STD_LOGIC_VECTOR ( 3 downto 0 ); DDR_dqs_p : inout STD_LOGIC_VECTOR ( 3 downto 0 ); FIXED_IO_mio : inout STD_LOGIC_VECTOR ( 53 downto 0 ); FIXED_IO_ddr_vrn : inout STD_LOGIC; FIXED_IO_ddr_vrp : inout STD_LOGIC; FIXED_IO_ps_srstb : inout STD_LOGIC; FIXED_IO_ps_clk : inout STD_LOGIC; FIXED_IO_ps_porb : inout STD_LOGIC; GPIO_I : in STD_LOGIC_VECTOR ( 5 downto 0 ); GPIO_O : out STD_LOGIC_VECTOR ( 5 downto 0 ); gpio_io_i : in STD_LOGIC_VECTOR ( 1 downto 0 ); gpio2_io_o : out STD_LOGIC_VECTOR ( 3 downto 0 ) ); end component system; signal GPIO_I : STD_LOGIC_VECTOR ( 5 downto 0 ); signal GPIO_O : STD_LOGIC_VECTOR ( 5 downto 0 ); begin system_i: component system port map ( DDR_addr(14 downto 0) => DDR_addr(14 downto 0), DDR_ba(2 downto 0) => DDR_ba(2 downto 0), DDR_cas_n => DDR_cas_n, DDR_ck_n => DDR_ck_n, DDR_ck_p => DDR_ck_p, DDR_cke => DDR_cke, DDR_cs_n => DDR_cs_n, DDR_dm(3 downto 0) => DDR_dm(3 downto 0), DDR_dq(31 downto 0) => DDR_dq(31 downto 0), DDR_dqs_n(3 downto 0) => DDR_dqs_n(3 downto 0), DDR_dqs_p(3 downto 0) => DDR_dqs_p(3 downto 0), DDR_odt => DDR_odt, DDR_ras_n => DDR_ras_n, DDR_reset_n => DDR_reset_n, DDR_we_n => DDR_we_n, 6 FIXED_IO_ddr_vrn => FIXED_IO_ddr_vrn, FIXED_IO_ddr_vrp => FIXED_IO_ddr_vrp, FIXED_IO_mio(53 downto 0) => FIXED_IO_mio(53 downto 0), FIXED_IO_ps_clk => FIXED_IO_ps_clk, FIXED_IO_ps_porb => FIXED_IO_ps_porb, FIXED_IO_ps_srstb => FIXED_IO_ps_srstb, GPIO_I(5 downto 0) => GPIO_I, GPIO_O(5 downto 0) => GPIO_O, gpio2_io_o(3 downto 0) => leds_gpio, gpio_io_i(1 downto 0) => sw_gpio ); leds_emio <= GPIO_O(5 downto 2); GPIO_I(1 downto 0) <= sw_emio; end STRUCTURE; Ajoutez ensuite un fichier de contrainte zc702_test_gpio.xdc qui contiendra : # connexion aux boutons poussoir SW5 – SW7 set_property PACKAGE_PIN G19 [get_ports {sw_emio[0]}]; set_property IOSTANDARD LVCMOS25 [get_ports {sw_emio[0]}]; set_property PACKAGE_PIN F19 [get_ports {sw_emio[1]}]; set_property IOSTANDARD LVCMOS25 [get_ports {sw_emio[1]}]; # # connexion aux leds ds15 – ds16 – ds17 – ds18 set_property PACKAGE_PIN V7 [get_ports {leds_emio[3]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_emio[3]}]; set_property PACKAGE_PIN W10 [get_ports {leds_emio[2]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_emio[2]}]; set_property PACKAGE_PIN P18 [get_ports {leds_emio[1]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_emio[1]}]; set_property PACKAGE_PIN P17 [get_ports {leds_emio[0]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_emio[0]}]; # # connexion aux boutons poussoir SW12.1 – SW12.2 set_property PACKAGE_PIN W7 [get_ports {sw_gpio[0]}]; set_property IOSTANDARD LVCMOS25 [get_ports {sw_gpio[0]}]; set_property PACKAGE_PIN W6 [get_ports {sw_gpio[1]}]; set_property IOSTANDARD LVCMOS25 [get_ports {sw_gpio[1]}]; # # connexion aux leds ds19 – ds20 – ds21 – ds22 set_property PACKAGE_PIN E15 [get_ports {leds_gpio[3]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_gpio[3]}]; set_property PACKAGE_PIN D15 [get_ports {leds_gpio[2]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_gpio[2]}]; set_property PACKAGE_PIN W17 [get_ports {leds_gpio[1]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_gpio[1]}]; set_property PACKAGE_PIN W5 [get_ports {leds_gpio[0]}]; set_property IOSTANDARD LVCMOS25 [get_ports {leds_gpio[0]}]; Finalement, vous pouvez générer le bitstream. 7 Pour créer Petalinux, il faut au minimum exporter le design : En effet, c’est la commande « Export Hardware » qui crée le fichier binaire .hdf (Hardware hanDoff File) qui contient la description complète du système que petalinux utilisera (comme le SDK en standalone d’ailleurs). 1.3 L’APPLICATION STANDALONE Testez maintenant le design en standalone. Il faut pour cela lancer le SDK, puis créer une application qui contiendra le code C suivant : #include #include #include #include #include <stdio.h> "xparameters.h" "xgpiops.h" "xgpio.h" "sleep.h" #define MIO_10 10 #define MIO_12 12 #define MIO_14 14 #define #define #define #define #define #define EMIO_54 EMIO_55 EMIO_56 EMIO_57 EMIO_58 EMIO_59 54 55 56 57 58 59 int main(void) { int i, Status; XGpioPs_Config *ConfigPtrPS; XGpioPs mio_emio; u8 data; 8 u32 Data_out, Data_in; XGpio Gpio; /* The Instance of the GPIO Driver */ xil_printf("test 1 : essai MIO\n"); // verrouillage ressource matérielle 0 ConfigPtrPS = XGpioPs_LookupConfig(0); if (ConfigPtrPS == NULL) { return XST_FAILURE; } // initialisation MIO XGpioPs_CfgInitialize(&mio_emio, ConfigPtrPS, ConfigPtrPS->BaseAddr); // validation et direction MIO XGpioPs_SetDirectionPin(&mio_emio, MIO_10, 1); // XGpioPs_SetOutputEnablePin(&mio_emio, MIO_10, 1); ds23, 1 = sortie XGpioPs_SetDirectionPin(&mio_emio, MIO_12, 0); // SW14, 0 = entrée XGpioPs_SetOutputEnablePin(&mio_emio, MIO_12, 0); XGpioPs_SetDirectionPin(&mio_emio, MIO_14, 0); // SW13, 0 = entrée XGpioPs_SetOutputEnablePin(&mio_emio, MIO_14, 0); for (i = 0; i < 32; i++) { Status = XGpioPs_ReadPin(&mio_emio, MIO_12); // lecture switches xil_printf("switch SW14 = %d\n", Status); Status = XGpioPs_ReadPin(&mio_emio, MIO_14); xil_printf("switch SW13 = %d\n", Status); XGpioPs_WritePin(&mio_emio, MIO_10, 0x0); // allumage leds usleep(200000); XGpioPs_WritePin(&mio_emio, MIO_10, 0x1); usleep(200000); } xil_printf("test 2 : essai EMIO\n"); // switches XGpioPs_SetDirectionPin(&mio_emio, EMIO_54, 0); // SW5 XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_54, 0); XGpioPs_SetDirectionPin(&mio_emio, EMIO_55, 0); // SW7 XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_55, 0); // leds XGpioPs_SetDirectionPin(&mio_emio, EMIO_56, 1); // XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_56, 1); XGpioPs_SetDirectionPin(&mio_emio, EMIO_57, 1); // XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_57, 1); XGpioPs_SetDirectionPin(&mio_emio, EMIO_58, 1); // XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_58, 1); XGpioPs_SetDirectionPin(&mio_emio, EMIO_59, 1); // XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_59, 1); ds15 ds16 ds17 ds18 data = 0; for (i = 0; i < 128; i++) { Status = XGpioPs_ReadPin(&mio_emio, EMIO_54); xil_printf("switch SW5 = %d\n", Status); Status = XGpioPs_ReadPin(&mio_emio, EMIO_55); xil_printf("switch SW7 = %d\n", Status); // switch reading XGpioPs_WritePin(&mio_emio, EMIO_56, data&0x01); 9 // leds counting XGpioPs_WritePin(&mio_emio, EMIO_57, (data>>1)&0x01); // leds counting XGpioPs_WritePin(&mio_emio, EMIO_58, (data>>2)&0x01); // leds counting XGpioPs_WritePin(&mio_emio, EMIO_59, (data>>3)&0x01); // leds counting data++; usleep(100000); } xil_printf("test 3 : essai AXI GPIO\n"); /* * Initialize the GPIO driver */ Status = XGpio_Initialize(&Gpio, 0); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * GPIO channel 1 = inputs * */ XGpio_SetDataDirection(&Gpio, 1, 0xffffffff); /* * GPIO channel 2 = outputs * */ XGpio_SetDataDirection(&Gpio, 2, 0x00000000); Data_out = 0; for (i = 0; i < 128; i++) { // les 4 bits faibles de data_out sont // envoyés sur channel 2 XGpio_DiscreteWrite(&Gpio, 2, Data_out); Data_out++; usleep(100000); // lecture dans l'ordre de BTND - BTNU Data_in = XGpio_DiscreteRead(&Gpio, 1); xil_printf("SW12-1 = %x\n", Data_in&0x00000001); xil_printf("SW12-2 = %x\n", (Data_in&0x00000002)>>1); } return 0; } REMARQUE : On ne peut pas tester le code directement depuis l’image VirtualBox car il manque la liaison JTAG. Mais on peut créer un fichier boot.bin pour démarrer depuis la SDcard (voir poly SOC Vivado page 143). Pour copier le fichier boot.bin sur /mnt/partage, il faut lancer le gestionnaire de fichier avec sudo. Dans un Terminal, tapez : sudo nautilus Allez ensuite dans le répertoire bootimage et copiez BOOT.bin dans le signet partage (en ayant au préalable supprimé le fichier existant) : 10 Sous Windows, il faut copier le fichier BOOT.BIN sur la SDcard, allumer la carte, lancer Teraterm puis vérifier le bon fonctionnement. Fermez tous les programmes ouverts. Allez dans le dossier zc702_test_gpio.sdk, puis déplacez les fichiers suivants dans un répertoire sauve créé à cet effet : Mais laissez le fichier system_wrapper.hdf. 1.4 CREATION DU PROJET PETALINUX Pour créer le projet Petalinux, vous devez ouvrir un Terminal et aller dans le dossier du projet Vivado : cd zc702_test_gpio Lancez la commande (attention, ce sont des doubles tirets) : petalinux-create --type project --template zynq --name zc702_peta 11 Le projet est créé dans le dossier zc702_peta : Avec quelques fichiers à l’intérieur : Il faut ensuite se placer dans le dossier du fichier HDF pour lancer la configuration : cd zc702_test_gpio.sdk petalinux-config --get-hw-description -p ../zc702_peta L’outil de configuration du système Linux démarre (il s’agit de buildroot, même si ce n’est pas marqué explicitement) : ↑↓ → ← 12 Cet outil permet de configurer le système graphiquement et de sauver la configuration dans le fichier : /home/fpga/zc702_test_gpio/zc702_peta/subsystems/linux/config. La configuration est une arborescence hiérarchique de propriétés que l’on peut activer ou désactiver. Les réglages intéressant se trouvent par exemple dans « Subsystem AUTO Hardware Settings ». Pour descendre dans ce menu, il faut le mettre en surbrillance puis taper sur la touche Entrée (↵). Dans le menu qui apparait alors, il faut sélectionner « Ethernet Settings » : Dans la salle de TP, nous sommes en adresse IP fixe (sans serveur DHCP). Il faut donc régler les paramètres IP à la main en descendant sur « Obtain IP address automatically » : 13 puis en appuyant sur la barre d’espace pour désactiver DHCP : On peut ensuite régler les paramètres manuellement en sélectionnant le paramètre, en appuyant sur Entrée puis en changeant la valeur : Au final, nous obtenons : 14 Pour remonter dans les menus, il faut sélectionner « Exit » en bas de la fenêtre puis taper sur Entrée : Nous sommes revenu dans « Subsystem AUTO Hardware Settings ». Sélectionnez « Advanced bootable images storage Settings » : Puis « boot image settings » : 15 C’est là que nous sélectionnerons le device de boot et le nom de l’image de boot (BOOT.BIN qui contiendra le FSBL, le bitstream du FPGA ainsi que le gestionnaire de boot de Linux, Uboot) : Les choix possibles sont la SDcard ou bien la mémoire flash de la carte. Laissons le réglage sur la SDcard : Remontons d’un niveau et allons dans « kernel image settings » : 16 C’est là que nous sélectionnerons le média contenant l’image Linux ainsi que son nom (image.ub au format FIT) : Les choix possibles sont Ethernet, la SDcard ou bien la mémoire flash de la carte. Laissons le réglage sur la SDcard : Remontons à la racine puis sélectionnons « Image Packaging Configuration » : 17 Puis « Root filesystem type » : C’est là que nous indiquerons où résidera le système de fichiers Linux (FS). Les choix possibles sont : Initial RAM file system le système de fichier est monté dans la RAM initial ramdisk même chose, mais ancienne méthode Journaling Flash File System le système de fichier est monté dans la flash Network File System le système de fichier est monté depuis un serveur NFS SD card le système de fichier est monté sur une deuxième partition Linux (EXTx FS) de la SD card. La première partition FAT32 contient seulement le fichier BOOT.BIN. 18 ATTENTION, on peut booter depuis la SDcard (ou en TFTP) avec les fichiers BOOT.BIN et image.ub tout en utilisant INITRAMFS pour loger le système de fichiers. C’est même le fonctionnement normal. Si le FS est dans la RAM, toutes les modifications effectuées sous Petalinux sont perdues quand la carte s'arrête. Si le FS est sur la SDcard, les modifications sont sauvegardées. Dans cet exemple, nous allons laisser le FS en INITRAMFS. On sort en faisant Exit plusieurs fois puis on sauve la configuration. On obtient dans le terminal après un moment : fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_test_gpio.sdk$ --get-hw-description -p ../zc702_peta INFO: Checking component... INFO: Getting hardware description... cp: omission du «/home/fpga/zc702_test_gpio/zc702_test_gpio.sdk/sauve» INFO: Rename system_wrapper.hdf to system.hdf petalinux-config répertoire ****** hsi v (64-bit) **** SW Build 1266856 on Fri Jun 26 16:35:25 MDT 2015 ** Copyright 1986-2015 Xilinx, Inc. All Rights Reserved. source /home/fpga/zc702_test_gpio/zc702_peta/build/linux/hw-description/hwdescription.tcl -notrace INFO: [Common 17-206] Exiting hsi at Fri Jun 24 14:23:57 2016... INFO: Config linux [INFO ] config linux configuration written to /home/fpga/zc702_test_gpio/zc702_peta/subsystems/linux/config *** End of the configuration. *** Execute 'make' to start the build or try 'make help'. [INFO ] generate DTS to /home/fpga/zc702_test_gpio/zc702_peta/subsystems/linux/configs/device-tree INFO: [Hsi 55-1698] elapsed time for repository loading 4 seconds INFO: [Common 17-206] Exiting hsi at Fri Jun 24 14:42:34 2016... [INFO ] generate linux/u-boot board header files INFO: [Hsi 55-1698] elapsed time for repository loading 0 seconds INFO: [Common 17-206] Exiting hsi at Fri Jun 24 14:42:36 2016... [INFO ] generate /home/fpga/zc702_test_gpio/zc702_peta/components/bootloader/zynq_fsbl INFO: [Hsi 55-1698] elapsed time for repository loading 0 seconds INFO: [Common 17-206] Exiting hsi at Fri Jun 24 14:42:48 2016... [INFO ] generate BSP for zynq_fsbl INFO: [Hsi 55-1698] elapsed time for repository loading 0 seconds INFO: [Common 17-206] Exiting hsi at Fri Jun 24 14:42:59 2016... INFO: Config linux/kernel [INFO ] oldconfig linux/kernel INFO: Config linux/rootfs [INFO ] oldconfig linux/rootfs Allons maintenant dans le répertoire de Petalinux : cd ../zc702_peta/ 19 Nous pouvons toujours relancer la configuration générale en tapant : petalinux-config Mais nous avons aussi accès à la configuration des programmes inclus dans le système de fichier (exemple, dropbear-openssh ou l'agent TCF pour le debug). petalinux-config -c rootfs Elle modifie le fichier de configuration : /home/fpga/zc702_test_gpio/ zc702_peta/subsystems/linux/configs/rootfs/config Dans notre cas, pour la mise au point des programmes, il faut activer dans le rootfs : Filesystem Packages base tcf-agent Target Communication Framework est la nouvelle méthode de communication d’Eclipse avec le système embarqué. Sortez en faisant Exit plusieurs fois puis sauvez la configuration. La commande : petalinux-config -c kernel permet la configuration du noyau (les drivers pour faire simple). Elle modifie le fichier : /home/fpga/ zc702_test_gpio/ zc702_peta/subsystems/linux/configs/kernel/config Dans notre cas, il faut activer le mode debug du système de fichiers : Kernel hacking Compile-time checks and compiler options 20 Debug Filesystem Puis les drivers des AXI GPIO et des MIO (ils sont normalement activés par défaut) : Device Drivers GPIO Support /sys/class/gpio/... (sysfs interface) Xilinx GPIO support Xilinx Zynq GPIO support Quand nous appuyons sur la barre d’espace, nous voyons apparaitre successivement <M> ou <*>. Quand le driver est marqué *, il est chargé automatiquement au boot de linux. Quand il est marqué M, il existe sous forme de module qui devra être chargé à la main avec la commande modprobe. Nous chargerons les drivers au boot avec l’*. 21 Il faut charger les drivers gpio-leds (ils sont normalement activés par défaut) : Device Drivers LED Support LED Class Support LED Support for GPIO connected LEDs Device Drivers LED Support LED Trigger support Sélectionner tous les triggers * Et gpio-keys ((ils sont normalement activés par défaut) : Device Drivers Input device support Keyboards GPIO Buttons Polled GPIO buttons Sortez en faisant Exit plusieurs fois puis sauvez la configuration. Maintenant que petalinux est configuré selon nos désirs, nous pouvons le créer avec la commande : petalinux-build Il faut compiler tous les fichiers, ce qui est assez long. Voici la sortie sur le Terminal : fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ petalinux-build INFO: Checking component... INFO: Generating make files and build linux INFO: Generating make files for the subcomponents of linux INFO: Building linux [INFO ] pre-build linux/rootfs/fwupgrade [INFO ] pre-build linux/rootfs/peekpoke [INFO ] pre-build linux/rootfs/uWeb [INFO ] build system.dtb [INFO ] build linux/kernel [INFO ] update linux/u-boot source [INFO ] generate linux/u-boot configuration files [INFO ] build linux/u-boot [INFO ] build zynq_fsbl [INFO ] Setting up stage config [INFO ] Setting up rootfs config [INFO ] Updating for cortexa9-vfp-neon [INFO ] Updating package manager [INFO ] Expanding stagefs [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [INFO ] build linux/rootfs/fwupgrade [INFO ] build linux/rootfs/peekpoke [INFO ] build linux/rootfs/uWeb [INFO ] build kernel in-tree modules [INFO ] modules linux/kernel [INFO ] post-build linux/rootfs/fwupgrade 22 dans la dans la [INFO ] post-build linux/rootfs/peekpoke [INFO ] post-build linux/rootfs/uWeb [INFO ] pre-install linux/rootfs/fwupgrade [INFO ] pre-install linux/rootfs/peekpoke [INFO ] pre-install linux/rootfs/uWeb [INFO ] install system.dtb [INFO ] install linux/kernel [INFO ] update linux/u-boot source [INFO ] generate linux/u-boot configuration files [INFO ] build linux/u-boot [INFO ] install linux/u-boot [INFO ] Expanding rootfs [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [INFO ] install sys_init [INFO ] install linux/rootfs/fwupgrade [INFO ] install linux/rootfs/peekpoke [INFO ] install linux/rootfs/uWeb [INFO ] install kernel in-tree modules [INFO ] modules_install linux/kernel [INFO ] post-install linux/rootfs/fwupgrade [INFO ] post-install linux/rootfs/peekpoke [INFO ] post-install linux/rootfs/uWeb [INFO ] package rootfs.cpio /home/fpga/zc702_test_gpio/zc702_peta/images/linux [INFO ] Update and install vmlinux image [INFO ] vmlinux linux/kernel [INFO ] install linux/kernel [INFO ] package zImage [INFO ] zImage linux/kernel [INFO ] install linux/kernel [INFO ] Package HDF bitstream [INFO ] Failed to copy images to TFTPBOOT /tftpboot fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ Après un certain temps (10 à 30 minutes, cela dépend du PC), le système est prêt. 1.5 TEST QEMU PUIS BOOT ZC702 Nous sommes toujours dans le dossier de création de Petalinux : fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ pwd /home/fpga/zc702_test_gpio/zc702_peta 23 dans la dans la dans la dans la dans la to Pour tester le système obtenu, on peut utiliser l’émulateur QEMU avec la commande (attention, ce sont des double-tirets --) : petalinux-boot --qemu --kernel QEMU démarre avec le fichier images/linux/zImage : fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ petalinux-boot --qemu -kernel INFO: The image provided is a zImage INFO: Set QEMU tftp to /home/fpga/zc702_test_gpio/zc702_peta/images/linux INFO: TCP PORT is free INFO: Starting arm QEMU INFO: qemu-system-aarch64 -L /home/fpga/Xilinx/petalinux-v2015.2.1final/etc/qemu -M arm-generic-fdt-plnx -machine linux=on -serial /dev/null -serial mon:stdio -display none -kernel /home/fpga/zc702_test_gpio/zc702_peta/build/qemu_image.elf -gdb tcp::9000 dtb /home/fpga/zc702_test_gpio/zc702_peta/images/linux/system.dtb -tftp /home/fpga/zc702_test_gpio/zc702_peta/images/linux -------------------------------------------------------------------Xilinx QEMU Aug 17 2015 17:27:04. -------------------------------------------------------------------Uncompressing Linux... done, booting the kernel. Booting Linux on physical CPU 0x0 Linux version 3.19.0-xilinx (fpga@fpga-VirtualBox) (gcc version 4.9.1 (Sourcery CodeBench Lite 2014.11-30) ) #2 SMP PREEMPT Fri Jun 24 15:03:22 CEST 2016 CPU: ARMv7 Processor [414fc091] revision 1 (ARMv7), cr=10c5387d CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache Machine model: zc702_peta bootconsole [earlycon0] enabled cma: Reserved 16 MiB at 0x3f000000 Memory policy: Data cache writealloc PERCPU: Embedded 9 pages/cpu @7e7d1000 s8128 r8192 d20544 u36864 Built 1 zonelists in Zone order, mobility grouping on. Total pages: 260096 Kernel command line: console=ttyPS0,115200 earlyprintk PID hash table entries: 4096 (order: 2, 16384 bytes) Dentry cache hash table entries: 131072 (order: 7, 524288 bytes) Inode-cache hash table entries: 65536 (order: 6, 262144 bytes) Memory: 1010836K/1048576K available (4725K kernel code, 254K rwdata, 1676K rodata, 5252K init, 207K bss, 21356K reserved, 16384K cma-reserved, 0K highmem) Virtual kernel memory layout: vector : 0xffff0000 - 0xffff1000 ( 4 kB) fixmap : 0xffc00000 - 0xfff00000 (3072 kB) vmalloc : 0x80800000 - 0xff000000 (2024 MB) lowmem : 0x40000000 - 0x80000000 (1024 MB) pkmap : 0x3fe00000 - 0x40000000 ( 2 MB) modules : 0x3f000000 - 0x3fe00000 ( 14 MB) .text : 0x40008000 - 0x40648980 (6403 kB) .init : 0x40649000 - 0x40b6a000 (5252 kB) .data : 0x40b6a000 - 0x40ba9a60 ( 255 kB) .bss : 0x40ba9a60 - 0x40bdd9f8 ( 208 kB) Preemptible hierarchical RCU implementation. RCU restricting CPUs from NR_CPUS=4 to nr_cpu_ids=2. RCU: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2 NR_IRQS:16 nr_irqs:16 16 L2C: platform modifies aux control register: 0x00000000 -> 0x30400000 L2C: DT/platform modifies aux control register: 0x00000000 -> 0x30400000 24 L2C-310 errata 588369 769419 enabled … … … ALSA device list: No soundcards found. Freeing unused kernel memory: 5252K (40649000 - 40b6a000) INIT: version 2.88 booting Creating /dev/flash/* device nodes random: dd urandom read with 0 bits of entropy available starting Busybox inet Daemon: inetd... done. Starting uWeb server: NET: Registered protocol family 10 update-rc.d: /etc/init.d/run-postinsts exists during rc.d purge (continuing) Removing any system startup links for run-postinsts ... /etc/rcS.d/S99run-postinsts INIT: Entering runlevel: 5 Configuring network interfaces... IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready route: resolving Starting tcf-agent: OK Built with PetaLinux v2015.2.1 (Yocto 1.8) zc702_peta /dev/ttyPS0 zc702_peta login: macb e000b000.ethernet eth0: link up (100/Full) IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready Built with PetaLinux v2015.2.1 (Yocto 1.8) zc702_peta /dev/ttyPS0 zc702_peta login: root Password: login[847]: root login on 'ttyPS0' root@zc702_peta:~# Le login/password est root/root (appuyez sur la touche entrée du clavier si le login n’apparait pas). Nous pouvons tester quelques commandes. Nous voyons que le réseau est bien configuré (sans gateway). root@zc702_peta:~# pwd /home/root root@zc702_peta:~# ifconfig eth0 Link encap:Ethernet HWaddr 00:0A:35:00:1E:53 inet addr:192.168.10.1 Bcast:192.168.10.255 Mask:255.255.255.0 inet6 addr: fe80::20a:35ff:fe00:1e53/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:15 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:2594 (2.5 KiB) Interrupt:28 Base address:0xb000 lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:4 errors:0 dropped:0 overruns:0 frame:0 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:236 (236.0 B) TX bytes:236 (236.0 B) 25 root@zc702_peta:~# route -n Kernel IP routing table Destination Gateway 192.168.10.0 0.0.0.0 Genmask 255.255.255.0 Flags Metric Ref U 0 0 Use Iface 0 eth0 Pour sortir de QEMU, il faut taper Ctrl+a puis x. root@zc702_peta:~# QEMU: Terminated fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ Il reste à créer le fichier BOOT.BIN avec la commande : cd images/linux On voit les fichiers de sortie créés par la commande petalinux-build : Puis (attention aux double-tirets) : petalinux-package --boot --fsbl zynq_fsbl.elf --fpga system_wrapper.bit --uboot --force Au démarrage de la carte, le fichier BOOT.BIN démarre en premier. Il lance ou charge : zynq_fsbl.elf system_wrapper.bit u-boot.elf // first stage bootloader // FGPA bitstream // second stage bootloader U-boot charge ensuite l’image Linux image.ub. 26 Il reste à copier les fichiers BOOT.BIN et image.ub sur la SDcard. Votre PC possède deux interfaces Ethernet, une reliée à Internet et une deuxième qui est libre (@IP = 192.168.10.2, mask = 255.255.255.0, pas de gateway). Reliez le port Ethernet de la ZC702 avec cette interface à l’aide d’un cordon Ethernet croisé. N’oubliez pas le port série. Il reste à démarrer la carte ZC702, puis à lancer GTKTerm (sous Ubuntu) ou bien TeraTerm (sous Windows). Le boot est normal et linux est configuré : root@zc702_peta:~# ifconfig eth0 Link encap:Ethernet HWaddr 00:0A:35:00:1E:53 inet addr:192.168.10.1 Bcast:192.168.10.255 Mask:255.255.255.0 inet6 addr: fe80::20a:35ff:fe00:1e53/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:926 (926.0 B) Interrupt:146 Base address:0xb000 lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:4 errors:0 dropped:0 overruns:0 frame:0 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:236 (236.0 B) TX bytes:236 (236.0 B) root@zc702_peta:~# route -n Kernel IP routing table Destination Gateway 192.168.10.0 0.0.0.0 Genmask 255.255.255.0 Flags Metric Ref U 0 0 Vous pouvez faire un ping vers le PC : root@zc702_peta:~# ping 192.168.10.2 PING 192.168.10.2 (192.168.10.2): 56 data bytes 64 bytes from 192.168.10.2: seq=0 ttl=64 time=0.508 64 bytes from 192.168.10.2: seq=1 ttl=64 time=0.230 64 bytes from 192.168.10.2: seq=2 ttl=64 time=0.160 64 bytes from 192.168.10.2: seq=3 ttl=64 time=0.173 27 ms ms ms ms Use Iface 0 eth0 Le système de fichiers est monté dans la RAM avec un FS temporaire tmpfs : root@zc702_peta:~# df Filesystem 1K-blocks devtmpfs 64 tmpfs 516252 tmpfs 516252 Used Available Use% Mounted on 0 64 0% /dev 12 516240 0% /run 20 516232 0% /var/volatile Il occupe environ 512 Moctets dans la mémoire. On peut voir l’occupation de chaque répertoire sous la racine avec la commande : du –sh /* : root@zc702_peta:~# du -sh /* 1.5M /bin 0 /boot 0 /dev 304.0K /etc 996.0K /home 0 /init 4.8M /lib 0 /media 0 /mnt 0 /proc 12.0K /run 708.0K /sbin 0 /sys 0 /tmp 2.4M /usr 528.0K /var Une des raisons de la faible occupation du système de fichiers Linux (12,6 Moctets) est l’utilisation de BusyBox. Les commandes ls –la /bin et ls –la /sbin montrent que les commandes Unix (qui sont normalement des programmes exécutables) sont ici des liens symboliques vers BusyBox : root@zc702_peta:~# ls -la /bin drwxr-xr-x 2 root root drwxrwxrwt 18 root root lrwxrwxrwx 1 root root lrwxrwxrwx 1 root root lrwxrwxrwx 1 root root lrwxrwxrwx 1 root root lrwxrwxrwx 1 root root lrwxrwxrwx 1 root root ... 3020 400 19 19 19 19 19 19 Jun Jan Jun Jun Jun Jun Jun Jun 24 2016 . 1 00:00 .. 24 2016 [ -> /bin/busybox.nosuid 24 2016 [[ -> /bin/busybox.nosuid 24 2016 ar -> /bin/busybox.nosuid 24 2016 ash -> /bin/busybox.nosuid 24 2016 awk -> /bin/busybox.nosuid 24 2016 basename -> /bin/busybox.nosuid BusyBox est un programme qui implémente un grand nombre de commandes Unix. Comme chaque fichier binaire exécutable comporte plusieurs kilooctets d'informations additionnelles, l'idée de combiner plus de deux cent programmes en un seul fichier exécutable permet de gagner une taille considérable. Son utilisation est classique en embarqué. 28 1.6 ACCES AUX MIO ET AXI GPIO VIA SYSFS Nous allons voir maintenant comment accéder aux MIO/EMIO et aux AXI GPIO depuis Petalinux. En standalone, l’accès se fait simplement en écrivant dans des registres placés dans la mémoire du système ARM avec des adresses physiques sur 32 bits. Sous Linux, les programmes n’ont plus accès aux adresses physiques car ils s’exécutent dans un environnement de mémoire virtuelle utilisateur. On appelle cet espace mémoire « User space ». Seul le noyau du système d’exploitation voit les adresses physiques. L’espace mémoire du noyau s’appelle « kernel space ». Pour un programme, l’accès aux ressources matérielles doit s’effectuer à l’aide d’un driver qui permet d’offrir une interface entre l’utilisateur et le kernel. Une méthode permet toutefois à l’utilisateur d’accéder aux GPIO en passant par le système de fichier : c’est sysfs. Le principal problème de sysfs, c’est l’identification du numéro de l’IO dans le système de fichier (FS). La méthode la plus simple consiste à activer le mode debug du système de fichier (fait précédemment) et de taper les commandes : root@zc702_peta:~# mount rootfs on / type rootfs (rw,size=505436k,nr_inodes=126359) proc on /proc type proc (rw,relatime) sysfs on /sys type sysfs (rw,relatime) debugfs on /sys/kernel/debug type debugfs (rw,relatime) devtmpfs on /dev type devtmpfs (rw,relatime,size=64k,nr_inodes=126359,mode=755) devpts on /dev/pts type devpts (rw,relatime,mode=600) tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755) tmpfs on /var/volatile type tmpfs (rw,relatime) On voit dans la liste des FS montés debugfs qui permet de debugger les GPIO avec la commande : cat /sys/kernel/debug/gpio. root@zc702_peta:~# cat /sys/kernel/debug/gpio GPIOs 900-903, /amba_pl/gpio@41200000: GPIOs 904-905, /amba_pl/gpio@41200000: GPIOs 906-1023, platform/e000a000.gpio, zynq_gpio: L’AXI GPIO était à l’adresse 0x41200000 dans le block design Vivado. /amba_pl/gpio@41200000 est donc notre AXI GPIO, 4 bits en sortie (900-903) et 2 bits en entrée EMIO (904-905). Les platform/e000a000.gpio sont à l’adresse 0xe000a000 dans le design. sont donc nos 54 MIO + 64 EMIO = 118 IO (906-1023). Cette méthode pour identifier les GPIO est la seule praticable. 29 Le tableau suivant résume la numérotation sysfs : MIO10 DS23 916 MIO12 SW14 918 MIO14 SW13 920 EMIO54 SW5 960 EMIO55 SW7 961 EMIO56 DS15 962 EMIO57 DS16 963 EMIO58 DS17 964 EMIO59 DS18 965 AXI GPIO0 SW12-1 904 AXI GPIO1 SW12-2 905 AXI GPIO2 DS22 900 AXI GPIO3 DS21 901 AXI GPIO4 DS20 902 AXI GPIO5 DS19 903 Dans le dossier /sys/class/gpio, on trouve les trois dossiers correspondants aux drivers déjà vu (gpiochip900, gpiochip904 et gpiochip906) plus les commandes export et unexport : root@zc702_peta:~# ls -la /sys/class/gpio drwxr-xr-x 2 root root 0 Jan 1 00:00 . drwxr-xr-x 41 root root 0 Jan 1 00:00 .. --w------1 root root 4096 Jan 1 00:00 export lrwxrwxrwx 1 root root 0 Jan 1 00:00 gpiochip900 -> ../../devices/virtual/gpio/gpiochip900 lrwxrwxrwx 1 root root 0 Jan 1 00:00 gpiochip904 -> ../../devices/virtual/gpio/gpiochip904 lrwxrwxrwx 1 root root 0 Jan 1 00:00 gpiochip906 -> ../../devices/soc0/amba/e000a000.gpio/gpio/gpiochip906 --w------1 root root 4096 Jan 1 00:00 unexport Pour tester une sortie, il faut copier le numéro de l’IO dans export (exemple avec MIO10) : echo 916 > /sys/class/gpio/export Un dossier gpio916 est créé : root@zc702_peta:~# ls /sys/class/gpio export gpio916 gpiochip900 gpiochip904 gpiochip906 Il faut ensuite lui donner un sens en écrivant out dans le fichier direction : echo out > /sys/class/gpio/gpio916/direction 30 unexport puis ensuite copier 0 ou 1 dans le fichier value : echo 1 > /sys/class/gpio/gpio916/value echo 0 > /sys/class/gpio/gpio916/value Pour supprimer le dossier gpio916 après usage, il faut copier le numéro dans unexport : echo 916 > /sys/class/gpio/unexport Le dossier gpio916 est détruit : root@zc702_peta:~# ls /sys/class/gpio export gpiochip900 gpiochip904 gpiochip906 unexport Pour tester une entrée, il faut copier le numéro de l’IO dans export (exemple avec AXI GPIO0) : echo 904 > /sys/class/gpio/export Un dossier gpio904 est créé. Il faut ensuite lui donner un sens en écrivant in dans le fichier direction : echo in > /sys/class/gpio/gpio904/direction On lit la valeur du bouton dans le fichier value : cat /sys/class/gpio/gpio904/value Pour supprimer le dossier gpio904 après usage, il faut copier le numéro dans unexport : echo 904 > /sys/class/gpio/unexport On accède donc à une IO en faisant simplement des lectures/écritures dans des fichiers. Tel est le principe de sysfs. 1.7 DEVELOPPEMENT D’UNE APPLICATION SYSFS Rien ne nous empêche maintenant de développer avec le SDK une application en C qui utilise sysfs. Pour cela, nous allons utiliser TCF pour développer sous Ubuntu (directement depuis le PC ou depuis l’image VirtualBox) et debugger sur la carte. Ouvrons le projet sous Vivado puis lançons le SDK. La carte ZC702 doit être accessible via internet. Créons une application helloworld linux : 31 C’est un programme C standard : 32 Il faut ensuite cliquer sur « add new target connection » : Et remplir les champs suivants avant de cliquer sur OK : La nouvelle connexion apparait : 33 Sélectionnez le projet test_sysfs dans le SDK, faites un clic droit puis sélectionnez « debug as », « Debug Configuration ». Sélectionnez « System Debugger », faites un clic droit puis « New ». Sur l’onglet « Target Setup », sélectionnez « Linux Application Debug » et la connexion zc702 : Sur l’onglet application, sélectionnez le projet avec le bouton « Browse » et saisissez /tmp/test_sysfs.elf dans le champ « Remote File Path » : 34 Cliquez sur Debug. Le programme démarre en perspective Debug. L’affichage se fait dans la console : Fermez le mode Debug. On peut aussi lancer en mode « Run As ». Sur la ZC702, nous pouvons visualiser le contenu de /tmp. Nous y voyons bien l’exécutable : Nous avons maintenant une méthodologie de développement. Dans le premier programme qui teste la led DS23, nous allons créer une fonction pour allouer une broche GPIO (gpio_alloc), une autre pour libérer la broche (gpio_free) afin d’alléger la fonction main(). On peut sortir avec un Ctrl+C dans la console du SDK, ce qui arrête le programme. Le code est le suivant : #include #include #include #include #include #include <stdio.h> <stdlib.h> <time.h> <fcntl.h> <unistd.h> <string.h> int gpio_alloc(char *num, char *dir) { int valuefd, exportfd, directionfd, l; char str[256]; // The GPIO has to be exported to be able to see it // in sysfs exportfd = open("/sys/class/gpio/export", O_WRONLY); if (exportfd < 0) { printf("Cannot open GPIO to export it\n"); exit(1); } l = strlen(num); write(exportfd, num, l+1); close(exportfd); //printf("GPIO exported successfully\n"); // Update the direction of the GPIO to be an output sprintf(str, "/sys/class/gpio/gpio%s/direction", num); 35 directionfd = open(str, O_RDWR); if (directionfd < 0) { printf("Cannot open GPIO direction it\n"); exit(1); } l = strlen(dir); write(directionfd, dir, l+1); close(directionfd); //printf("GPIO direction set successfully\n"); // Get the GPIO value ready to be toggled sprintf(str, "/sys/class/gpio/gpio%s/value", num); if (l == 2) valuefd = open(str, O_RDONLY); // in else valuefd = open(str, O_WRONLY); // out if (valuefd < 0) { printf("Cannot open GPIO value\n"); exit(1); } return valuefd; } void gpio_free(char *num) { int fd; fd = open("/sys/class/gpio/unexport", O_WRONLY); write(fd, num, strlen(num)); close(fd); } int main() { int out916, i; printf("GPIO test running...\n"); out916 = gpio_alloc("916", "out"); printf("GPIO value opened, now toggling...\n"); for(i = 0; i < 1000000; i++) { write(out916,"1", 2); usleep(200000); write(out916,"0", 2); usleep(200000); } 36 gpio_free("916"); printf("End\n"); return 0; } La led DS23 clignote et s’arrête au moment du Ctrl+C à condition d’avoir cliqué dans la console au préalable. En commentant les 2 lignes usleep(200000); nous pouvons chronométrer le temps pris par 1 million d’itérations, soit environ 4 secondes. Cela nous donne 2 µs par écriture. Dans le deuxième programme, seule la fonction main() change. Ce programme copie l’état de SW14 sur la led DS15. Il s’exécute en 6 secondes environ, soit 4 µs par lecture, à cause de la fonction lseek() qui remet le fichier au début après chaque lecture. int main() { int out962, in918, i; char val; printf("GPIO test running...\n"); out962 = gpio_alloc("962", "out"); in918 = gpio_alloc("918", "in"); printf("GPIO value opened, now toggling and reading...\n"); //printf("Press Ctrl + c to exit...\n"); // toggle the GPIO as fast a possible forever, a control c is needed // to stop it for(i = 0; i < 1000000; i++) { lseek(in918, 0, SEEK_SET); read(in918, &val, 1); if (val == '0') write(out962,"0", 2); if (val == '1') write(out962,"1", 2); } gpio_free("918"); gpio_free("962"); printf("End\n"); return 0; } Dans ce dernier programme, seule la fonction main() change. Nous allons tester toutes les I/O. int main() { int in918, in920, in960, in961, in904, in905, i; int out916, out900, out901, out902, out903, out962, out963, out964, out965; 37 char val; printf("GPIO test running...\n"); in918 in920 in960 in961 in904 in905 out916 out900 out901 out902 out903 out962 out963 out964 out965 = = = = = = gpio_alloc("918", gpio_alloc("920", gpio_alloc("960", gpio_alloc("961", gpio_alloc("904", gpio_alloc("905", = = = = = = = = = gpio_alloc("916", gpio_alloc("900", gpio_alloc("901", gpio_alloc("902", gpio_alloc("903", gpio_alloc("962", gpio_alloc("963", gpio_alloc("964", gpio_alloc("965", "in"); "in"); "in"); "in"); "in"); "in"); "out"); "out"); "out"); "out"); "out"); "out"); "out"); "out"); "out"); printf("GPIO value opened, now toggling and reading...\n"); //printf("Press Ctrl + c to exit...\n"); // toggle the GPIO as fast a possible forever, a control c is needed // to stop it for(i = 0; i < 1000; i++) { write(out916,"1", 2); write(out902,"1", 2); write(out903,"1", 2); usleep(25000); write(out916,"0", 2); write(out902,"0", 2); write(out903,"0", 2); usleep(25000); lseek(in918, 0, SEEK_SET); read(in918, &val, 1); if (val == '0') write(out962,"0", 2); if (val == '1') write(out962,"1", 2); lseek(in920, 0, SEEK_SET); read(in920, &val, 1); if (val == '0') write(out963,"0", 2); if (val == '1') write(out963,"1", 2); lseek(in960, 0, SEEK_SET); read(in960, &val, 1); if (val == '0') write(out964,"0", 2); if (val == '1') write(out964,"1", 2); 38 lseek(in961, 0, SEEK_SET); read(in961, &val, 1); if (val == '0') write(out965,"0", 2); if (val == '1') write(out965,"1", 2); lseek(in904, 0, SEEK_SET); read(in904, &val, 1); if (val == '0') write(out900,"0", 2); if (val == '1') write(out900,"1", 2); lseek(in905, 0, SEEK_SET); read(in905, &val, 1); if (val == '0') write(out901,"0", 2); if (val == '1') write(out901,"1", 2); } gpio_free("916"); gpio_free("918"); gpio_free("920"); gpio_free("960"); gpio_free("961"); gpio_free("962"); gpio_free("963"); gpio_free("964"); gpio_free("965"); gpio_free("900"); gpio_free("901"); gpio_free("902"); gpio_free("903"); gpio_free("904"); gpio_free("905"); printf("End\n"); return 0; } 1.8 INSERTION DE L’APPLICATION SYSFS DANS LA DISTRIBUTION PETALINUX Nous avons maintenant un programme au format ELF et nous désirons l’insérer dans le projet petalinux existant. Comme le FS est dans la RAM, le programme /tmp/test_sysfs.elf disparaitra au redémarrage de la carte. Fermez le SDK et Vivado, ouvrez un Terminal et placez-vous dans le dossier racine de petalinux : cd /home/fpga/zc702_test_gpio/zc702_peta puis créez le squelette d’une application avec la commande : 39 petalinux-create -t apps --template install --name test_sysfs --enable Il faut ensuite éditer le Makefile dans : Pour obtenir : Sauvez le Makefile, puis remplacez le script créé automatiquement : 40 Par le fichier ELF de l’application à insérer (test_sysfs.elf ): En le renommant test_sysfs. Vous devez finalement voir le fichier ELF renommé ici : Il ne reste plus qu’à l’ajouter dans la distribution petalinux existante avec la commande : petalinux-build -c rootfs/test_sysfs fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ petalinux-build rootfs/test_sysfs INFO: Checking component... INFO: Generating make files and build linux/rootfs/test_sysfs INFO: Generating make files for the subcomponents linux/rootfs/test_sysfs INFO: Building linux/rootfs/test_sysfs [INFO ] pre-build linux/rootfs/test_sysfs [INFO ] build linux/rootfs/test_sysfs [INFO ] post-build linux/rootfs/test_sysfs [INFO ] pre-install linux/rootfs/test_sysfs [INFO ] install linux/rootfs/test_sysfs -c of Puis à recréer les fichiers dans /home/fpga/zc702_test_gpio/zc702_peta/images/linux : petalinux-build -x package fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ petalinux-build -x package INFO: Checking component... INFO: Generating make files and build linux INFO: Generating make files for the subcomponents of linux INFO: Building linux [INFO ] package rootfs.cpio to /home/fpga/zc702_test_gpio/zc702_peta/images/linux 41 [INFO [INFO [INFO [INFO [INFO [INFO [INFO [INFO ] ] ] ] ] ] ] ] Update and install vmlinux image vmlinux linux/kernel install linux/kernel package zImage zImage linux/kernel install linux/kernel Package HDF bitstream Failed to copy images to TFTPBOOT /tftpboot Il reste à copier le nouveau fichier image.ub sur la Sdcard, puis à booter la carte. Nous pouvons vérifier la présence dans /bin du programme test_sysfs ainsi que son exécution. 1.9 LES DRIVERS GPIO-LEDS ET GPIO-KEYS Le fichier image.ub contient : • le noyau Linux (dans un fichier tel que vmlinux), • le système de fichier Linux monté dans un ramdisk (rootfs.cpio.gz), • le device tree blob (system.dtb). Quand u-boot lance l’image, le noyau est d’abord chargé dans la SDRAM. Il lit ensuite le DTB qui va servir à le configurer en fonction des ressources matérielles disponibles avant de démarrer. Puis rootfs.cpio.gz est décompacté dans le rootfs qui se trouve en RAM et le FS Linux est monté. 42 Dans un PC, les adresses des ressources matérielles sont connues à l’avance et on peut les coder en dur dans le noyau, mais ce n’est pas possible dans un système ARM car les configurations possibles sont trop nombreuses. On utilise donc un device tree pour contenir les informations décrivant le système. On part d’une arborescence de plusieurs fichiers DTS (device tree source) qui contiennent la structure arborescente décrivant le système. A l’aide d’un compilateur (dtc : device tree compiler), on crée un fichier DTB (device tree blob) qui est son équivalent binaire. Le noyau Linux lit le DTB pour connaitre la configuration du système. Les fichiers source se trouvent dans le dossier : Le nombre et les noms de ces fichiers ont souvent changé avec les versions de petalinux. Le fichier racine est system-top.dts qui se contente d’inclure system-conf.dtsi. Le fichier DTS (Device Tree Source) inclut directement ou indirectement tous les fichiers DTSI (Device Tree Source Include) : Le fichier system-conf.dtsi inclut les autres fichiers : 43 On a donc l’arborescence de fichiers suivante : system-top.dts system-conf.dtsi skeleton.dtsi zynq-7000.dtsi pcw.dtsi pl.dtsi Ils ont été créés lors du premier petalinux-build à partir du fichier HDF. Il y a 2 fichiers qui nous intéressent particulièrement : • zynq-7000.dtsi qui décrit les ressources de notre système ARM, • pl.dtsi qui décrit les ressources des périphériques réalisés dans la logique programmable (PL). Pour pouvoir utiliser les drivers gpio-leds et gpio-keys, nous devons les rajouter dans le device tree. Pour cela, nous allons ajouter un fichier en copiant pl.dtsi : Puis en le renommant led-nodes.dtsi : Nous allons l’inclure dans system-top.dts : 44 Le contenu de led-nodes.dtsi doit être le code suivant (conservez le commentaire en début de fichier sinon le fichier ne sera pas vu au Build) : / { gpio-leds { compatible = "gpio-leds"; led-ds22 { label = "led-ds22"; gpios = <&axi_gpio_0 0 1>; default-state = "on"; linux,default-trigger = "heartbeat"; }; }; gpio-keys { compatible = "gpio-keys"; #address-cells = <1>; #size-cells = <0>; autorepeat; sw14 { label = " sw14"; gpios = <&gpio0 12 0>; linux,code = <108>; /* down */ gpio-key,wakeup; autorepeat; }; sw13 { label = "sw13"; gpios = <&gpio0 14 0>; linux,code = <103>; /* up */ gpio-key,wakeup; autorepeat; }; }; }; La ligne compatible = "xxx"; indique le nom du driver à utiliser. La ligne gpios = <&axi_gpio_0 0 1>; indique le nom du périphérique marqué dans pl.dtsi (voir figure suivante) suivi du numéro de la broche (led ds22) puis du niveau d’activation (actif à 1). Le label sera utilisé dans le code en C pour identifier la led. La ligne linux,defaulttrigger = "heartbeat"; définit le comportement par défaut de la led (ici, un double clignotement lent). 45 Sw13 et sw14 étant connectés sur des MIO, le nom du périphérique est indiqué dans le fichier zynq-7000.dtsi suivi du numéro de la broche : La ligne linux,code = <108>; permet d’associer un code 8 bits à l’appui sur le bouton. Quand tout est prêt, il faut se placer dans le dossier de petalinux : cd /home/fpga/zc702_test_gpio/zc702_peta puis lancer : petalinux-build En cas d’erreur, vous pouvez voir des informations plus détaillées dans le fichier build.log : 46 fpga@fpga-VirtualBox:~/zc702_test_gpio/zc702_peta$ petalinux-build INFO: Checking component... INFO: Generating make files and build linux INFO: Generating make files for the subcomponents of linux INFO: Building linux [INFO ] pre-build linux/rootfs/fwupgrade … … … … [INFO ] install system.dtb [INFO ] install linux/kernel [INFO ] update linux/u-boot source [INFO ] generate linux/u-boot configuration files [INFO ] build linux/u-boot [INFO ] install linux/u-boot [INFO ] Expanding rootfs [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [ERROR] dpkg : avertissement : « start-stop-daemon » introuvable variable PATH ou non exécutable [INFO ] install sys_init [INFO ] install linux/rootfs/fwupgrade [INFO ] install linux/rootfs/peekpoke [INFO ] install linux/rootfs/test_sysfs [INFO ] install linux/rootfs/uWeb [INFO ] install kernel in-tree modules [INFO ] modules_install linux/kernel [INFO ] post-install linux/rootfs/fwupgrade [INFO ] post-install linux/rootfs/peekpoke [INFO ] post-install linux/rootfs/test_sysfs [INFO ] post-install linux/rootfs/uWeb [INFO ] package rootfs.cpio /home/fpga/zc702_test_gpio/zc702_peta/images/linux [INFO ] Update and install vmlinux image [INFO ] vmlinux linux/kernel [INFO ] install linux/kernel [INFO ] package zImage [INFO ] zImage linux/kernel [INFO ] install linux/kernel [INFO ] Package HDF bitstream [INFO ] Failed to copy images to TFTPBOOT /tftpboot dans la dans la dans la dans la dans la to Dans le dossier /home/fpga/zc702_test_gpio/zc702_peta/images/linux, on trouve le nouveau fichier system.dtb. On peut recréer dans un fichier unique, le source DTS correspondant avec la commande : dtc -I dtb -O dts -o system.dts system.dtb Dans le fichier system.dts obtenu, on retrouve nos deux drivers : 47 Il reste à copier la nouvelle image.ub sur la SDcard, puis à booter la zc702. La ds22 se met à clignoter (à cause de la propriété heartbeat). On peut voir l’activité des boutons poussoir avec la commande : cat /dev/input/event0 | hexdump A chaque appui sur un bouton, 4 nouvelles lignes apparaissent (peu importe leur signification pour l’instant, il y a bien détection de l’appui) : 48 Nous pouvons maintenant développer un programme en C avec la méthode vue dans sysfs. Voici le premier programme simple qui permet de faire clignoter la led ds22. #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <time.h> <fcntl.h> <signal.h> <pthread.h> <unistd.h> <linux/input.h> int main() { int tmp; /* Configure LED */ tmp = open("/sys/class/leds/led-ds22/trigger", O_WRONLY); if (tmp < 0) { printf("Error opening led trigger"); return 1; } if (write(tmp, "default-on", 10) != 10) { printf("Error writing on led trigger"); return 1; } close(tmp); printf("LED ready for use\n"); tmp = open("/sys/class/leds/led-ds22/brightness", O_WRONLY); if (tmp < 0) { printf("Error opening led brightness"); exit(1); } printf("LED blinking, Ctrl+C to exit\n"); while (1) { write(tmp, "1", 2); usleep(50000); write(tmp, "0", 2); usleep(50000); } } Le driver gpio-keys permet d’associer un code à l’appui/relachement sur une touche. On détecte l’évènement en lisant /dev/input/event0. Le code associé à la touche est défini dans led-nodes.dtsi. Dans ce deuxième exemple, l’appui sur sw14 allume la led ds22 et le relâchement éteint la led ds22 : #include #include #include #include <stdio.h> <stdlib.h> <time.h> <fcntl.h> 49 #include #include #include #include <signal.h> <pthread.h> <unistd.h> <linux/input.h> int main() { struct input_event ev; int led, button; int key_code; int size = sizeof(ev); /* Configure LED */ led = open("/sys/class/leds/led-ds22/trigger", O_WRONLY); if (led < 0) { printf("Error opening led trigger"); return 1; } if (write(led, "default-on", 10) != 10) { printf("Error writing on led trigger"); return 1; } close(led); printf("LED ds22 ready for use\n"); led = open("/sys/class/leds/led-ds22/brightness", O_WRONLY); if (led < 0) { printf("Error opening led brightness"); exit(1); } /* Read event0 */ button = open("/dev/input/event0", O_RDONLY); if (button < 0) { printf("\nOpen " "/dev/input/event0" " failed!\n"); return 1; } printf("buttons ready for use\n"); printf("press sw14 to turn led ds22 on, release sw14 to turn led ds22 off\n"); /* Read and parse event */ while (1) { if (read(button, &ev, size) < size) { printf("\nReading from " "/dev/input/event0" " failed!\n"); return 1; } if (ev.value == 1 && ev.type == 1) { /* event + press key_code = ev.code; if (key_code == 108) { /* sw14 */ write(led, "0", 2); /* turn on the led */ } } if (ev.value == 0 && ev.type == 1) { key_code = ev.code; 50 */ /* event + release */ if (key_code == 108) { /* sw14 */ write(led, "1", 2); /* turn off the led */ } } usleep(1000); } printf("Ctrl+C to exit\n"); } Le troisième programme permet d’allumer et d’éteindre la led ds22 en appuyant sur sw14 et sw12. C’est le code qui permet de différentier les boutons : #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <time.h> <fcntl.h> <signal.h> <pthread.h> <unistd.h> <linux/input.h> int main() { struct input_event ev; int led, button; int key_code; int size = sizeof(ev); /* Configure LED */ led = open("/sys/class/leds/led-ds22/trigger", O_WRONLY); if (led < 0) { printf("Error opening led trigger"); return 1; } if (write(led, "default-on", 10) != 10) { printf("Error writing on led trigger"); return 1; } close(led); printf("LED ds22 ready for use\n"); led = open("/sys/class/leds/led-ds22/brightness", O_WRONLY); if (led < 0) { printf("Error opening led brightness"); exit(1); } /* Read event0 */ button = open("/dev/input/event0", O_RDONLY); if (button < 0) { printf("\nOpen " "/dev/input/event0" " failed!\n"); return 1; } printf("buttons ready for use\n"); printf("sw14 turns ds22 on, sw12 turns ds22 off\n"); 51 /* Read and parse event */ while (1) { if (read(button, &ev, size) < size) { printf("\nReading from " "/dev/input/event0" " failed!\n"); return 1; } if (ev.value == 1 && ev.type == 1) { /* event + press only */ key_code = ev.code; if (key_code == 108) { /* sw14 */ write(led, "0", 2); } else if (key_code == 103) { /* sw12 */ write(led, "1", 2); } usleep(1000); } } printf("Ctrl+C to exit\n"); } On peut faire des programmes plus compliqués, multi-thread par exemple, comme dans l’exemple suivant : #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <time.h> <fcntl.h> <signal.h> <pthread.h> <unistd.h> <linux/input.h> #define LED_BRIGHTNESS #define LED_TRIGGER #define INPUT_EVENT #define LED_MAX_SPEED #define PERIOD_COEFF "/sys/class/leds/led-ds22/brightness" "/sys/class/leds/led-ds22/trigger" "/dev/input/event0" 10 16000 unsigned int led_speed; pthread_mutex_t lock; /* Blink LED */ static void *LEDMod(void *dummy) { unsigned int led_period; int tmp; tmp = open(LED_BRIGHTNESS, O_WRONLY); if (tmp < 0) exit(1); while (1) { pthread_mutex_lock(&lock); led_period = (LED_MAX_SPEED - led_speed) * PERIOD_COEFF; pthread_mutex_unlock(&lock); write(tmp, "1", 2); usleep(led_period); write(tmp, "0", 2); usleep(led_period); 52 } } int main() { pthread_t pth; struct input_event ev; int tmp; int key_code; int size = sizeof(ev); /* Configure LED */ led_speed = 5; tmp = open(LED_TRIGGER, O_WRONLY); if (tmp < 0) return 1; if (write(tmp, "default-on", 10) != 10) { printf("Error writing trigger"); return 1; } close(tmp); printf("Configured LED for use\n"); /* Create thread */ pthread_mutex_init(&lock, NULL); pthread_create(&pth, NULL, LEDMod, "Blinking LED..."); /* Read event0 */ tmp = open(INPUT_EVENT, O_RDONLY); if (tmp < 0) { printf("\nOpen " INPUT_EVENT " failed!\n"); return 1; } /* Read and parse event, update global variable */ while (1) { if (read(tmp, &ev, size) < size) { printf("\nReading from " INPUT_EVENT " failed!\n"); return 1; } if (ev.value == 1 && ev.type == 1) { /* Down press only */ key_code = ev.code; if (key_code == KEY_DOWN) { /* lower speed */ /* Protect from concurrent read/write */ pthread_mutex_lock(&lock); if (led_speed > 0) led_speed -= 1; pthread_mutex_unlock(&lock); } else if (key_code == KEY_UP) { /* raise speed */ pthread_mutex_lock(&lock); if (led_speed < 9) led_speed += 1; pthread_mutex_unlock(&lock); } printf("Speed: %i\n", led_speed); usleep(1000); } } } 53 Ce programme ne compile pas correctement : Il faut ajouter la librairie multi-thread. Pour cela, sélectionnez le projet dans le SDK (test_gpio dans cet exemple), faites un clic droit puis cliquez sur « Properties ». Dans la fenêtre qui s’ouvre, sélectionnez « C/C++ Build », « Settings » et cliquez sur l’onglet « Tool Settings ». Allez dans « ARM Linux gcc linker », « Inferred Options », « Software Platform ». Cliquez sur « Add… » Puis tapez -lpthread dans la fenêtre suivante : 54 Cliquez sur OK pour sortir des deux fenêtres, le programme recompile automatiquement sans erreurs. Lancez le programme. La fréquence de clignotement de la led ds22 est incrémentée par sw13 et décrémentée par sw14 : 1.10 ACCES AUX REGISTRES VIA PEEK ET POKE Le problème avec les méthodes précédentes, c’est que les écritures et les lectures se font fil à fil et qu’il n’est pas possible de réaliser des écritures par bank 32 bits. Au-delà de la vitesse pure des I/O (quelques µs), les accès 32 bits doivent être réalisés bit par bit, ce qui rend les transferts très lents. Il existe un appel système qui permet de mapper directement les adresses de kernel space dans user space, c’est mmap(). On le retrouvera dans le chapitre suivant. Deux programmes natifs de Linux utilisant mmap() permettent de réaliser des tests en ligne de commande : • peek = lecture d’une adresse 32 bits, • poke = écriture d’un mot sur 32 bits dans une adresse 32 bits. Tout d’abord, nous devons supprimer les drivers gpio-leds et gpio-keys de linux en retirant le fichier led-nodes.dtsi du device tree, c’est-à-dire en supprimant la ligne d’include dans system-top.dts : 55 Quand tout est prêt, il faut se placer dans le dossier de petalinux : cd /home/fpga/zc702_test_gpio/zc702_peta puis lancer : petalinux-build Il reste à copier le nouveau fichier image.ub sur la SDcard, puis à booter la zc702. Les drivers gpio-leds et gpio-keys étant supprimés (pour éviter un conflit), nous allons pouvoir utiliser peek et poke. Il faut maintenant déterminer comment utiliser les GPIO en accédant à leurs registres internes. C’est ce que font les différentes fonctions du programme zc702_test_gpio écrit en standalone. On peut donc lire la documentation et debugger le programme standalone en mode pas à pas pour repérer les échanges. Pour la documentation, il faut utiliser : • Pour les MIO/EMIO, le document UG585 (SoC Technical Reference Manual V1.10). 1863 pages d’explications, c’est indigeste mais complet (Le TRM pour les intimes). Ce qui nous intéresse se trouve chapitre 14 (page 381) et à l’annexe B.19 page 1348. • Pour les AXI GPIO, le document AXI GPIO V2.0 Product Guide (pg144-axi-gpio.pdf). C’est le paragraphe « Register Space » page 9 qui nous intéresse. Commençons par les horloges. Le bloc AXI_GPIO est relié à l’horloge FCLK_CLK0 qui a une fréquence égale à 100 MHz (voir block design). Pour les MIO/EMIO, c’est plus compliqué. Au §14.4.1 du TRM, on voit que l’horloge du contrôleur est cpu_1x. Dans le chapitre 25 du TRM, table 25-3 page 686 exemple 1, on voit que la fréquence est égale à 1/6 de la fréquence du processeur, soit 111 MHz. Pour voir l’état des horloges au démarrage de Petalinux, il faut taper la commande : cat /sys/kernel/debug/clk/clk_summary La ligne : fclk0 1 1 99999999 00 nous indique que clk0 vaut bien 100 MHz et que l’horloge est active. 56 Par contre, la ligne : gpio_aper 0 0 111111110 00 nous indique que l’horloge cpu_1x reliée au contrôleur MIO/EMIO est désactivée (mais la fréquence est bien égale à 111 MHz). Pour activer cette horloge, il faut mettre à 1 le bit 22 du registre APER_CLK_CTRL (voir table 25.2 page 685 du TRM) qui se trouve à l’adresse 0xF800012C (voir TRM page 1596). Nous allons donc : 1. lire le registre, 2. faire un OU avec 0x400000 pour mettre à 1 le bit 22, 3. écrire le résultat dans le registre. Avec peek et poke, cela donne les commandes suivantes : peek 0xf800012c (résultat = 0x00a40444) poke 0xf800012c 0x00e40444 peek 0xf800012c (résultat = 0x00e40444 pour vérifier) Inutile de refaire une commande cat pour vérifier, le fichier clk_summary ne contient que l’état des horloges au démarrage. Testons les AXI GPIO à l’aide du tableau suivant : 57 Channel1 est en entrée. Il faut écrire 0xffffffff dans GPIO_TRI à l’adresse 0x41200000 + 0x04. L’adresse 0x41200000 de l’AXI_GPIO a été fixée dans le « block design » : Channel2 est en sortie. Il faut écrire 0x00000000 dans GPIO2_TRI à l’adresse 0x41200000 + 0x0C. Ensuite, on lit dans GPIO_DATA à l’adresse 0x41200000 + 0x00 et on écrit dans GPIO2_DATA à l’adresse 0x41200000 + 0x08. Cela nous donne les commandes suivantes : poke 0x41200004 0xffffffff (channel 1 en entrée) poke 0x4120000C 0x00000000 (channel 2 en sortie) peek 0x41200000 (lecture sw12-1 sw12-2) poke 0x41200008 0x0000000f (mise à 1 leds ds19-ds22) poke 0x41200008 0x00000000 (mise à 0 leds ds19-ds22) Activez les switches sw12-1 et sw12-2 Passons aux MIO/EMIO. Nous sommes sur la bank2 avec les bits 0-1 en entrée et les bits 2-5 en sortie. L’adresse de base du contrôleur est en 0xE000A000 (voir page 1348 du TRM). Les étapes suivantes doivent être suivies : 1) désactiver les interruptions en écrivant 0xffffffff dans le registre INT_DIS_2 (offset 0x0294). 58 2) Donner la direction des broches en mettant à 0 les entrées et à 1 les sorties en écrivant 0x0000003C dans le registre DIRM_2 (offset 0x0284) puis relire pour vérifier. 3) Activer les sorties en les mettant à 1 en écrivant 0x0000003C dans le registre OEN_2 (offset 0x0288) puis relire pour vérifier. 4) Lire les valeurs dans le registre DATA_2_RO (offset 0x0068). 5) Ecrire : • En 32 bits. On écrit dans DATA_2 (offset 0x0048). • En 16 bits. On écrit dans MASK_DATA_2_LSW (offset 0x0010). Les 16 bits forts forment le masque, les 16 bits faibles forment la data. Pour que le bit de données soit écrit, le bit correspondant dans le masque doit être mis à 0. Cela nous donne les commandes suivantes : poke 0XE000A294 0xffffffff (désactive les IT) poke 0xE000A284 0x0000003C (dirm2 4 sorties, 2 entrées) peek 0xE000A284 (vérification) poke 0xE000A288 0x0000003C (oen2, sorties validées) peek 0xE000A288 (vérification) peek 0xE000A068 (lecture sw5 sw7) poke 0xE000A048 0x0000003C (mise à 1 leds ds15-ds18 : écriture 32 bits) poke 0xE000A010 0xFFC30000 (mise à 0 leds ds15-ds18 : écriture 16 bits) 1.11 DEVELOPPEMENT D’UNE APPLICATION UTILISANT /DEV/MEM « /dev/mem » est un fichier virtuel représentant la mémoire du système. Pour accéder à une adresse physique depuis l’espace utilisateur (user space), nous pouvons ouvrir « /dev/mem » et utiliser l’appel système mmap() pour mapper une page de mémoire physique dans l’espace de mémoire virtuel de l’utilisateur. Nous pourrons ensuite, à l’aide d’un pointeur à l’intérieur de cette page, réaliser des lectures et des écritures 32 bits. Cette méthode peut être dangereuse si elle est mal utilisée (on peut mapper n’importe quelle adresse, y compris à l’intérieur du noyau (kernel) de Linux) mais elle est très simple et ne nécessite ni driver ni modification du device tree. On peut faire des essais préalables avec peek et poke, puis écrire le programme correspondant. Mais la gestion des interruptions avec mmap() seule est impossible. Il faut de plus connaitre les adresses physiques (en debuggant avec le programme standalone) et 59 plusieurs threads ne peuvent accéder simultanément aux mêmes adresses (aucune protection n’est possible contre les accès multiples). Les opérations suivantes sont nécessaires : Déterminer la taille d’une page mémoire (4 kilo avec Petalinux) page_size=sysconf(_SC_PAGESIZE); Ouvrir /dev/mem fd = open ("/dev/mem", O_RDWR); Donner l’adresse du registre gpio_addr = 0xf800012c; L’adresse de début de page a ses 3 quartets de poids faible à 0, soit 0xf8000000 page_addr = (gpio_addr & (~(page_size-1))); Calcul de l’offset de l’adresse réelle, ici c’est 0x012c page_offset = gpio_addr - page_addr; On récupère dans un pointeur l’adresse de début de page ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); On peut lire n’importe où dans la page de 4 kilo value = *((volatile unsigned long *)(ptr + page_offset)); On peut écrire n’importe où dans la page de 4 kilo *((volatile unsigned long *)(ptr + page_offset)) = value; On libère la page munmap(ptr, page_size); On ferme /dev/mem close(fd); Voici le programme en C équivalent aux commandes peek et poke du § précédent : #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <sys/mman.h> <fcntl.h> <time.h> int main() { int fd; unsigned long gpio_addr; unsigned int value, i, data; unsigned long page_addr, page_offset; void *ptr; unsigned page_size=sysconf(_SC_PAGESIZE); printf("GPIO access through /dev/mem. Page size = %x\n", page_size); /* Open /dev/mem file */ fd = open ("/dev/mem", O_RDWR); if (fd < 1) { printf("Unable to open /dev/mem\n"); return -1; } // // GPIO_APER enable 60 // gpio_addr = 0xf800012c; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); value = *((volatile unsigned long *)(ptr + page_offset)); // Read value value = value | 0x00400000; // bit 22 = 1 *((volatile unsigned long *)(ptr + page_offset)) = value; // write value munmap(ptr, page_size); printf("GPIO APER enabled\n"); // // AXI GPIO configuration // gpio_addr = 0x41200000; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); // channel1 = input *((volatile unsigned long *)(ptr + page_offset + 0x04)) = 0xffffffff; // channel2 = output *((volatile unsigned long *)(ptr + page_offset + 0x0c)) = 0x00000000; printf("AXI GPIO configured\n"); for(i = 0; i < 10000000; i++) { // button reading value = *((volatile unsigned long *)(ptr + page_offset + 0x00)); data = 0; if ((value data if ((value data & = & = 0x00000001) != 0) data | 0x00000003; // sw12-2 turns led ds19 ds20 on 0x00000002) != 0) data | 0x0000000C; // sw12-1 turns led ds22 ds21 on // write on leds *((volatile unsigned long *)(ptr + page_offset + 0x08)) = data; } munmap(ptr, page_size); printf("End of AXI GPIO test\n"); // // EMIO configuration // gpio_addr = 0xE000A000; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; 61 ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); // IT disabled *((volatile unsigned long *)(ptr + page_offset + 0x0294)) = 0xffffffff; // pin direction *((volatile unsigned long *)(ptr + page_offset + 0x0284)) = 0x0000003C; // output enabled *((volatile unsigned long *)(ptr + page_offset + 0x0288)) = 0x0000003C; printf("EMIO bank 2 configured\n"); for(i = 0; i < 25000000; i++) { // button reading value = *((volatile unsigned long *)(ptr + page_offset + 0x0068)); data = 0; if ((value data if ((value data & = & = 0x00000001) != 0) data | 0x0000000C; // sw5 turns led ds15-ds16 on 0x00000002) != 0) data | 0x00000030; // sw7 turns led ds17-ds18 on // write on leds *((volatile unsigned long *)(ptr + page_offset + 0x0048)) = data; } munmap(ptr, page_size); close(fd); printf("End of program\n"); return 0; } Nous sommes maintenant en lecture/écriture sur 32 bits. Les vitesses suivantes ont été mesurées : Mode Debug Mode Release AXI GPIO en écriture 225 ns 200 AXI GPIO en lecture 200 ns 170 EMIO en écriture 140 ns 120 EMIO en lecture 120 ns 100 1.12 DEVELOPPEMENT D’UNE APPLICATION UTILISANT UIO FRAMEWORK La méthode précédente basée sur l’appel système mmap() et « /dev/mem » est suffisante pour prototyper, mais pas pour un développement pérenne. User space I/O (UIO) framework est la méthode généralement utilisée pour éviter le développement d’un driver spécifique. Elle permet de faire tout ce que fait mmap() avec « /dev/mem » mais de manière plus propre avec 62 en plus la gestion des interruptions si nécessaire. Ceci dit, rien n’empêche de combiner les deux approches : par exemple « /dev/mem » pour modifier la valeur d’un registre afin d’activer une horloge, et UIO pour piloter des EMIO et des AXI GPIO. C’est ce que nous allons faire maintenant en reprenant le programme du paragraphe précédent. Pour commencer, il faut se placer dans le dossier de petalinux : cd /home/fpga/zc702_test_gpio/zc702_peta puis lancer : petalinux-config -c kernel Il faut activer (c’est normalement déjà le cas par défaut) : Device Drivers Userspace I/O drivers Userspace I/O platform driver with generic IRQ handling Il faut ensuite modifier le device tree en allant dans le dossier suivant : Il faut ajouter la commande suivante dans le bootargs du fichier system-conf.dtsi : uio_pdrv_genirq.of_id=generic-uio 63 afin d’obtenir : Cette option de boot est nécessaire à partir de Petalinux 2015.2 pour charger le framework UIO. Il faut ensuite changer le driver (generic-uio) des AXI GPIO ainsi que son label (axi_gpio) dans pl.dtsi : Puis changer le driver (generic-uio) des EMIO ainsi que son label (emio) dans zynq7000.dtsi : 64 Il reste à lancer : petalinux-build Copiez le nouveau fichier image.ub sur la SDcard, puis bootez la zc702. On doit voir les deux drivers UIO, à savoir uio0 et uio1 dans /dev : Le répertoire /sys/class/uio donne des informations supplémentaires sur le matériel à travers sysfs. On voit maintenant les deux dossiers uio0 et uio1 : Allons dans uio0. Nous allons y trouver des fichiers ou bien d’autres dossiers (comme maps ou power) : Dans le fichier name, on retrouve le label des EMIO (celui que nous avons modifié) : Descendons dans maps, puis dans map0. Nous y retrouvons les informations contenues dans le device tree, notamment l’adresse de base (addr = 0xe000a000) du contrôleur EMIO ainsi que sa plage d’utilisation (son range à savoir size = 0x00001000) : 65 Chaque contrôleur a une adresse de base et un range (ci-dessous, le contrôleur AXI_GPIO) : On retrouve dans uio1 les mêmes informations pour les AXI GPIO (addr = 0x4120000, range = 0x00010000) : 66 Pour développer l’application, la méthode est plus simple qu’avec « /dev/mem » car la gestion des adresses est déjà gérée par UIO. Il ne reste qu’à s’occuper de l’offset pour accéder aux bons registres. Il n’y a pas d’interruptions dans ce design. Les opérations suivantes sont nécessaires : Ouvrir le driver fd0 = open("/dev/uio0", O_RDWR); Récupérer dans un pointeur l’adresse de début de plage de fonctionnement du contrôleur (pour un EMIO, c’est 4 kilo soit 0x1000. Pour un AXI GPIO, c’est 64 kilo, soit 0x10000) ptr_emio = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd0, 0); On peut lire n’importe où dans la plage value = *((volatile unsigned long *)(ptr_emio + 0x0068)); On peut écrire n’importe où dans la plage *((volatile unsigned long *)(ptr_emio + 0x0048)) = data; On libère la plage munmap(ptr_emio, 0x1000); On ferme le driver close(fd0); Voici le programme en C équivalent au programme du paragraphe précédent : #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <sys/mman.h> <fcntl.h> <time.h> int main() { int i; int fd0, fd1; int fd; unsigned long gpio_addr; unsigned int value, data; unsigned long page_addr, page_offset; void *ptr; void *ptr_emio, *ptr_axi_gpio; unsigned page_size=sysconf(_SC_PAGESIZE); /* Open /dev/mem file */ fd = open ("/dev/mem", O_RDWR); if (fd < 1) { printf("Unable to open /dev/mem\n"); return -1; } // // GPIO_APER enable // 67 gpio_addr = 0xf800012c; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); value = *((volatile unsigned long *)(ptr + page_offset)); // Read value value = value | 0x00400000; // bit 22 = 1 *((volatile unsigned long *)(ptr + page_offset)) = value; // write value munmap(ptr, page_size); close(fd); printf("GPIO APER enabled\n"); // // UIO configuration // /* Open the UIO device file */ fd0 = open("/dev/uio0", O_RDWR); if (fd0 < 1) { printf("Invalid UIO device file: /dev/uio0.\n"); return -1; } fd1 = open("/dev/uio1", O_RDWR); if (fd1 < 1) { printf("Invalid UIO device file: /dev/uio1.\n"); return -1; } /* mmap the UIO devices */ ptr_emio = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd0, 0); ptr_axi_gpio = mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0); // // AXI GPIO configuration // // channel1 = input *((volatile unsigned long*)(ptr_axi_gpio + 0x04)) = 0xffffffff; // channel2 = output *((volatile unsigned long*)(ptr_axi_gpio + 0x0C)) = 0x00000000; printf("AXI GPIO configured\n"); for(i = 0; i < 10000000; i++) { // button reading value = *((volatile unsigned long *)(ptr_axi_gpio + 0x00)); data = 0; if ((value data if ((value data & = & = 0x00000001) != 0) data | 0x00000003; // sw12-2 turns led ds19 ds20 on 0x00000002) != 0) data | 0x0000000C; // sw12-1 turns led ds22 ds21 on // write on leds 68 *((volatile unsigned long *)(ptr_axi_gpio + 0x08)) = data; } printf("End of AXI GPIO test\n"); // // EMIO configuration // // IT disabled *((volatile unsigned long *)(ptr_emio + 0x0294)) = 0xffffffff; // pin direction *((volatile unsigned long *)(ptr_emio + 0x0284)) = 0x0000003C; // output enabled *((volatile unsigned long *)(ptr_emio + 0x0288)) = 0x0000003C; printf("EMIO bank 2 configured\n"); for(i = 0; i < 25000000; i++) { // button reading value = *((volatile unsigned long *)(ptr_emio + 0x0068)); data = 0; if ((value data if ((value data & = & = 0x00000001) != 0) data | 0x0000000C; // sw5 turns led ds15-ds16 on 0x00000002) != 0) data | 0x00000030; // sw7 turns led ds17-ds18 on // write on leds *((volatile unsigned long *)(ptr_emio + 0x0048)) = data; } munmap(ptr_emio, 0x1000); munmap(ptr_axi_gpio, 0x10000); close(fd0); close(fd1); printf("End of program.\n"); return 0; } Les vitesses suivantes ont été mesurées : Mode Debug Mode Release AXI GPIO en écriture 225 ns 200 ns AXI GPIO en lecture 200 ns 170 ns EMIO en écriture 140 ns 120 ns EMIO en lecture 120 ns 100 ns Il n’y a pas de différences notables de vitesse de transfert entre « /dev/mem » et UIO. 69 70 2 LES GPIO SOUS LINUX AVEC IRQ 2.1 LE DESIGN DE TEST VIVADO Nous allons reprendre le design précédent en y ajoutant une interruption pour l’AXI GPIO. Sauvez le projet précédent puis supprimez les répertoires zc702_peta et zc702_test_gpio.sdk dans le dossier zc702_test_gpio : Ouvrez le projet sous Vivado. Ouvrez le block design puis double-cliquez sur l’IP Zynq. Activez les interruptions en cochant les cases suivantes, puis cliquez sur OK : Vous noterez que la numérotation des interruptions commence à 61. Une entrée IRQ_F2P apparait sur le composant : 71 Double cliquez sur le composant AXI GPIO et cochez la case « Enable Interrupt » puis cliquez sur OK : Une sortie ip2intc_irpt apparait sur le composant : Reliez maintenant les deux broches d’interruptions ensemble : 72 Au final, vous devez obtenir : Validez le block design, puis sauvez-le. Régénérez les produits de sortie, puis générez le bitstream. Finalement, exportez le design. Il faut noter qu’avec le rebond mécanique des boutons poussoir, même en plaçant des temporisations aux bons endroits dans le code C, il est probable que les interruptions soient prises en compte plusieurs fois. Cela ne sera pas gênant pour nos tests. 2.2 LES APPLICATIONS STANDALONE Dans le SDK, nous allons créer une première application test_axi_gpio qui va tester la détection de l’interruption 61 à l’aide des boutons SW12-1 et SW12-2. Il faut savoir que l’IT 61 sera déclenchée à l’appui et au relâchement de chaque bouton (détection du changement d’état). Le code C est le suivant : #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> "xil_io.h" "xil_exception.h" "xparameters.h" "xil_cache.h" "xil_printf.h" "xil_types.h" "xscugic.h" "sleep.h" "xgpio.h" #define INTC_DEVICE_ID #define INTC_DEVICE_INT_ID XPAR_SCUGIC_0_DEVICE_ID 61 void DeviceDriverHandler(void *CallbackRef); /* * Create a shared variable to be used by the main thread of processing and * the interrupt processing */ volatile static int flag_end = 0; 73 int main(void) { int Status; XScuGic InterruptController; /* Instance of the Interrupt Controller */ XScuGic_Config *GicConfig; /* The configuration parameters of the controller */ XGpio Gpio; /* The Instance of the GPIO Driver */ xil_printf("AXI GPIO + GIC Example Test\r\n"); /* * Initialize the GPIO driver */ Status = XGpio_Initialize(&Gpio, 0); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * GPIO channel 1 = inputs * */ XGpio_SetDataDirection(&Gpio, 1, 0xffffffff); /* * GPIO channel 2 = outputs * */ XGpio_SetDataDirection(&Gpio, 2, 0x00000000); /* * Initialize the interrupt controller driver so that it is ready to * use. */ GicConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == GicConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(&InterruptController, GicConfig, GicConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } // trigger = 1, level sensitive. trigger = 3, edge sensitive XScuGic_SetPriorityTriggerType(&InterruptController,INTC_DEVICE_INT_ID,0xA0,0x3); /* * Connect the interrupt controller interrupt handler to the hardware * interrupt handling logic in the ARM processor. */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, &InterruptController); /* * Connect a device driver handler that will be called when an * interrupt for the device occurs, the device driver handler performs 74 * the specific interrupt processing for the device */ Status = XScuGic_Connect(&InterruptController, INTC_DEVICE_INT_ID, (Xil_ExceptionHandler)DeviceDriverHandler, (void *)&Gpio); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * Enable the interrupt for the device */ XScuGic_Enable(&InterruptController, INTC_DEVICE_INT_ID); /* * Enable the GPIO channel interrupts so that push buttons can be * detected and enable interrupts for the GPIO device */ XGpio_InterruptEnable(&Gpio, 0x00000001); XGpio_InterruptGlobalEnable(&Gpio); XGpio_InterruptClear(&Gpio, 0x00000001); /* * Enable interrupts in the ARM */ Xil_ExceptionEnable(); /* * Wait for the interrupt to be processed, if the interrupt does not * occur this loop will wait forever */ while (flag_end == 0) { usleep(10000); } XScuGic_Disconnect(&InterruptController, INTC_DEVICE_INT_ID); xil_printf("End of Test\r\n"); return XST_SUCCESS; } void DeviceDriverHandler(void *CallbackRef) { static u32 count = 0, bntd_flag = 0, bntu_flag = 0; u32 Data_in; XGpio *Gpio = (XGpio *)CallbackRef; XGpio_InterruptDisable(Gpio, 0x00000001); Data_in = XGpio_DiscreteRead(Gpio, 1); if ((Data_in & 0x00000001) != 0) { xil_printf("SW12-1 on\r\n"); bntd_flag = 1; } if ((bntd_flag == 1) && (Data_in == 0)) { 75 xil_printf("SW12-1 off\r\n"); bntd_flag = 0; } if ((Data_in & 0x00000002) != 0) { xil_printf("SW12-2 on\r\n"); bntu_flag = 1; } if ((bntu_flag == 1) && (Data_in == 0)) { xil_printf("SW12-2 off\r\n"); bntu_flag = 0; } count++; usleep(50000); // anti rebond if(count == 10) flag_end = 1; XGpio_InterruptClear(Gpio, 0x00000001); XGpio_InterruptEnable(Gpio, 0x00000001); } Le traitement est assez rudimentaire. L’IRQ est détectée pour tout changement sur channel 1. Il faut donc mémoriser l’état précédent des entrées pour savoir ce qui a changé. Le résultat est le suivant : La deuxième application, test_emio, va tester la détection de l’interruption 52 (voir TRM page 384) à l’aide des boutons SW5 et SW7 sur front montant. Ici, la gestion des interruptions est plus complexe car il y a 4 bank pour les MIO/EMIO. Nous allons traiter l’IT en deux temps : 1) La fonction MyXGpioPs_IntrHandler recherche la bank activée et son statut. 2) La fonction IntrHandler traite l’IT dans cette bank. 76 Le code C est le suivant : #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> "xil_io.h" "xil_exception.h" "xparameters.h" "xil_cache.h" "xil_printf.h" "xil_types.h" "xscugic.h" "sleep.h" "xgpiops.h" #define INTC_DEVICE_ID #define INTC_DEVICE_INT_ID XPAR_SCUGIC_0_DEVICE_ID 52 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID void IntrHandler(void *CallBackRef, u32 Bank, u32 Status); void MyXGpioPs_IntrHandler(XGpioPs *InstancePtr); /* * Create a shared variable to be used by the main thread of processing and * the interrupt processing */ volatile static int flag_end = 0; int main(void) { int Status; XScuGic InterruptController; /* Instance of the Interrupt Controller */ XScuGic_Config *GicConfig; /* The configuration parameters of the controller */ XGpioPs Gpio; XGpioPs_Config *ConfigPtr; xil_printf("EMIO + GIC Example Test\r\n"); /* * MIO configuration */ ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); if (ConfigPtr == NULL) { return XST_FAILURE; } XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, 54, 0); // SW5, 0 = entrée XGpioPs_SetDirectionPin(&Gpio, 55, 0); // SW7, 0 = entrée /* * GIC configuration */ GicConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == GicConfig) { return XST_FAILURE; } 77 Status = XScuGic_CfgInitialize(&InterruptController, GicConfig, GicConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * Connect the interrupt controller interrupt handler to the hardware * interrupt handling logic in the ARM processor. */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, &InterruptController); /* * Connect a device driver handler that will be called when an * interrupt for the device occurs, the device driver handler performs * the specific interrupt processing for the device */ Status = XScuGic_Connect(&InterruptController, INTC_DEVICE_INT_ID, (Xil_ExceptionHandler)MyXGpioPs_IntrHandler, (void *)&Gpio); if (Status != XST_SUCCESS) { return Status; } /* * Enable rising edge interrupts for pin MIO50, MIO51. */ XGpioPs_SetIntrTypePin(&Gpio, 54, XGPIOPS_IRQ_TYPE_EDGE_RISING); XGpioPs_SetIntrTypePin(&Gpio, 55, XGPIOPS_IRQ_TYPE_EDGE_RISING); /* * Set the user handler for gpio interrupts. */ XGpioPs_SetCallbackHandler(&Gpio, (void *)&Gpio, IntrHandler); /* * Enable the GPIO interrupts of pin. */ XGpioPs_IntrEnablePin(&Gpio, 54); XGpioPs_IntrEnablePin(&Gpio, 55); /* * Enable the interrupt for the device */ XScuGic_Enable(&InterruptController, INTC_DEVICE_INT_ID); /* * Clear the specified pending interrupt. */ XGpioPs_IntrClearPin(&Gpio, 54); XGpioPs_IntrClearPin(&Gpio, 55); /* * Enable interrupts in the ARM */ 78 Xil_ExceptionEnable(); /* * Wait for the interrupt to be processed, if the interrupt does not * occur this loop will wait forever */ while (flag_end == 0) { usleep(10000); } XScuGic_Disconnect(&InterruptController, INTC_DEVICE_INT_ID); xil_printf("End of Test\r\n"); return XST_SUCCESS; } /****************************************************************************/ /** * This function is the user layer callback function * * @param CallBackRef is a pointer to the upper layer callback reference. * @param Status is the Interrupt status of the GPIO bank. * * @return None. * * @note None. * ******************************************************************************/ void IntrHandler(void *CallBackRef, u32 Bank, u32 Status) { static u32 count = 0; // // xil_printf("Interrupt n°%d detected\r\n", count); xil_printf("Bank = %d, Status = %x\r\n", Bank, Status); if ((Status & 0x01 ) != 0) xil_printf("SW5 activation\r\n"); if ((Status & 0x02 ) != 0) xil_printf("SW7 activation\r\n"); count++; if(count == 10) flag_end = 1; } /*****************************************************************************/ /** * * This function is the interrupt handler for GPIO interrupts.It checks the * interrupt status registers of all the banks to determine the actual bank in * which an interrupt has been triggered. It then calls the upper layer callback * handler set by the function XGpioPs_SetBankHandler(). The callback is called * when an interrupt * * @param InstancePtr is a pointer to the XGpioPs instance. * * @return None. * 79 * @note This function does not save and restore the processor context * such that the user must provide this processing. * ******************************************************************************/ void MyXGpioPs_IntrHandler(XGpioPs *InstancePtr) { u8 Bank; u32 IntrStatus; u32 IntrEnabled; Xil_AssertVoid(InstancePtr != NULL); Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); for (Bank = 0U; Bank < XGPIOPS_MAX_BANKS; Bank++) { IntrStatus = XGpioPs_IntrGetStatus(InstancePtr, Bank); IntrEnabled = XGpioPs_IntrGetEnabled(InstancePtr, Bank); // xil_printf("Bank = %d, IntStatus = %x, IntEnabled = %x\r\n", Bank, IntrStatus, IntrEnabled); if ((IntrStatus != (u32)0)&&(IntrEnabled == (u32)3)) { usleep(50000); // anti rebond IntrEnabled = XGpioPs_IntrGetEnabled(InstancePtr, Bank); XGpioPs_IntrClear((XGpioPs *)InstancePtr, Bank, (IntrStatus & IntrEnabled)); // appel de IntrHandler InstancePtr->Handler(InstancePtr-> CallBackRef, Bank, (IntrStatus & IntrEnabled)); } } } Le résultat est le suivant : 80 La troisième application, test_mio, va tester la détection de l’interruption 52 à l’aide des boutons SW14 et SW13 sur front montant. Le code est similaire au précédent : #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> "xil_io.h" "xil_exception.h" "xparameters.h" "xil_cache.h" "xil_printf.h" "xil_types.h" "xscugic.h" "sleep.h" "xgpiops.h" #define INTC_DEVICE_ID #define INTC_DEVICE_INT_ID XPAR_SCUGIC_0_DEVICE_ID 52 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID void IntrHandler(void *CallBackRef, u32 Bank, u32 Status); void MyXGpioPs_IntrHandler(XGpioPs *InstancePtr); /* * Create a shared variable to be used by the main thread of processing and * the interrupt processing */ volatile static int flag_end = 0; int main(void) { int Status; XScuGic InterruptController; /* Instance of the Interrupt Controller */ XScuGic_Config *GicConfig; /* The configuration parameters of the controller */ XGpioPs Gpio; XGpioPs_Config *ConfigPtr; xil_printf("MIO + GIC Example Test\r\n"); /* * MIO configuration */ ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); if (ConfigPtr == NULL) { return XST_FAILURE; } XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, 12, 0); // SW14, 0 = entrée XGpioPs_SetDirectionPin(&Gpio, 14, 0); // SW13, 0 = entrée /* * GIC configuration */ GicConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == GicConfig) { 81 return XST_FAILURE; } Status = XScuGic_CfgInitialize(&InterruptController, GicConfig, GicConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * Connect the interrupt controller interrupt handler to the hardware * interrupt handling logic in the ARM processor. */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, &InterruptController); /* * Connect a device driver handler that will be called when an * interrupt for the device occurs, the device driver handler performs * the specific interrupt processing for the device */ Status = XScuGic_Connect(&InterruptController, INTC_DEVICE_INT_ID, (Xil_ExceptionHandler)MyXGpioPs_IntrHandler, (void *)&Gpio); if (Status != XST_SUCCESS) { return Status; } /* * Enable rising edge interrupts for pin MIO12, MIO14. */ XGpioPs_SetIntrTypePin(&Gpio, 12, XGPIOPS_IRQ_TYPE_EDGE_RISING); XGpioPs_SetIntrTypePin(&Gpio, 14, XGPIOPS_IRQ_TYPE_EDGE_RISING); /* * Set the user handler for gpio interrupts. */ XGpioPs_SetCallbackHandler(&Gpio, (void *)&Gpio, IntrHandler); /* * Enable the GPIO interrupts of pin. */ XGpioPs_IntrEnablePin(&Gpio, 12); XGpioPs_IntrEnablePin(&Gpio, 14); /* * Enable the interrupt for the device */ XScuGic_Enable(&InterruptController, INTC_DEVICE_INT_ID); /* * Clear the specified pending interrupt. */ XGpioPs_IntrClearPin(&Gpio, 12); XGpioPs_IntrClearPin(&Gpio, 14); /* 82 * Enable interrupts in the ARM */ Xil_ExceptionEnable(); /* * Wait for the interrupt to be processed, if the interrupt does not * occur this loop will wait forever */ while (flag_end == 0) { usleep(10000); } XScuGic_Disconnect(&InterruptController, INTC_DEVICE_INT_ID); xil_printf("End of Test\r\n"); return XST_SUCCESS; } /****************************************************************************/ /** * This function is the user layer callback function * * @param CallBackRef is a pointer to the upper layer callback reference. * @param Status is the Interrupt status of the GPIO bank. * * @return None. * * @note None. * ******************************************************************************/ void IntrHandler(void *CallBackRef, u32 Bank, u32 Status) { static u32 count = 0; // // xil_printf("Interrupt n°%d detected\r\n", count); xil_printf("Bank = %d, Status = %x\r\n", Bank, Status); if ((Status & 0x1000 ) xil_printf("SW14 if ((Status & 0x4000 ) xil_printf("SW13 != 0) activation\r\n"); != 0) activation\r\n"); count++; if(count == 10) flag_end = 1; } /*****************************************************************************/ /** * * This function is the interrupt handler for GPIO interrupts.It checks the * interrupt status registers of all the banks to determine the actual bank in * which an interrupt has been triggered. It then calls the upper layer callback * handler set by the function XGpioPs_SetBankHandler(). The callback is called * when an interrupt * * @param InstancePtr is a pointer to the XGpioPs instance. * 83 * @return None. * * @note This function does not save and restore the processor context * such that the user must provide this processing. * ******************************************************************************/ void MyXGpioPs_IntrHandler(XGpioPs *InstancePtr) { u8 Bank; u32 IntrStatus; u32 IntrEnabled; Xil_AssertVoid(InstancePtr != NULL); Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); for (Bank = 0U; Bank < XGPIOPS_MAX_BANKS; Bank++) { IntrStatus = XGpioPs_IntrGetStatus(InstancePtr, Bank); IntrEnabled = XGpioPs_IntrGetEnabled(InstancePtr, Bank); //xil_printf("Bank = %d, IntStatus = %x, IntEnabled = %x\r\n", Bank, IntrStatus, IntrEnabled); if ((IntrStatus != (u32)0)&&(IntrEnabled == 0x5000)) { usleep(50000); // anti rebond IntrEnabled = XGpioPs_IntrGetEnabled(InstancePtr, Bank); XGpioPs_IntrClear((XGpioPs *)InstancePtr, Bank, (IntrStatus & IntrEnabled)); // appel de IntrHandler InstancePtr->Handler(InstancePtr-> CallBackRef, Bank, (IntrStatus & IntrEnabled)); } } } Le résultat est le suivant : 84 2.3 CREATION DU PROJET PETALINUX AVEC UIO La création de Petalinux se déroule comme au chapitre précédent en faisant attention aux points suivants : • Dans la configuration générale, réglages de l’adresse IP. • Dans la configuration rootfs, activation de tcf-agent. • Dans la configuration kernel, activation de debug filesystem et de UIO. Dans le device tree, il faut penser à ajouter la commande suivante dans le bootargs du fichier system-conf.dtsi : uio_pdrv_genirq.of_id=generic-uio afin d’obtenir : Il faut ensuite changer le driver (generic-uio) des EMIO ainsi que son label (emio) dans zynq-7000.dtsi : Intéressons-nous au triplet <0 20 4> sur la ligne interrupts. Le triplet <X Y Z> signifie : • Il y a trois types d’interruption dans le PS ; SPI (Shared Peripheral Interrupts), PPI (Private Peripheral Interrupts) et SGI (Software Generated Interrupts). Nos interruptions, 52 et 61 sont des SPI. Si X = 0, il faut retirer 32 à la valeur de l’interruption. Si X = 1, il faut retirer 16 à la valeur de l’interruption. 85 • Y = numéro de l’interruption sous Linux. Comme X = 0 et que l’IT vaut 52, Y = 52 – 32 soit 20. • Z représente le type d’activation par défaut de l’IT selon le tableau suivant. 0 aucun 1 Front montant 2 Front descendant 3 Les deux fronts 4 Niveau haut 8 Niveau bas Il faut changer le driver (generic-uio) des AXI GPIO ainsi que son label (axi_gpio) dans pl.dtsi : Le numéro d’IT est égal à 61-32 = 29. Nous pouvons maintenant créer une distribution petalinux. Au boot de la carte, nous pouvons vérifier les interruptions grâce à la commande : cat /proc/interrupts 86 On voit à la ligne 23 le contrôleur EMIO avec l’IT n°52 et à la ligne 47 le contrôleur axi_gpio avec l’IT n°61 : Les interruptions étant correctement configurées, nous pouvons passer à l’application logicielle. 2.4 DEVELOPPEMENT DES APPLICATIONS UIO FRAMEWORK Dans le SDK, nous allons créer une première application test_axi_gpio_uio qui va tester la détection de l’interruption à l’aide des switches SW12-2 et SW12-1. Le code C est le suivant : #include #include #include #include #include #include #define #define #define #define <stdio.h> <stdlib.h> <unistd.h> <sys/mman.h> <fcntl.h> <time.h> GPIO_DATA_OFFSET 0x00 GPIO_TRI_OFFSET 0x04 GPIO_DATA2_OFFSET 0x08 GPIO_TRI2_OFFSET 0x0C 87 #define GPIO_GLOBAL_IRQ 0x11C #define GPIO_IRQ_CONTROL 0x128 #define GPIO_IRQ_STATUS 0x120 volatile static int flag_end = 0; inline void reg_write(void *reg_base, unsigned long offset, unsigned long value) { *((volatile unsigned long *)(reg_base + offset)) = value; } inline unsigned long reg_read(void *reg_base, unsigned long offset) { return *((volatile unsigned long *)(reg_base + offset)); } unsigned int get_memory_size(char *sysfs_path_file) { FILE *size_fp; unsigned int size; // open the file that describes the memory range size that is based on the // reg property of the node in the device tree size_fp = fopen(sysfs_path_file, "r"); if (size_fp == NULL) { printf("unable to open the uio size file\n"); exit(-1); } // get the size which is an ASCII string such as 0xXXXXXXXX and then be stop // using the file fscanf(size_fp, "0x%08X", &size); fclose(size_fp); return size; } void wait_for_interrupt(int fd, void *gpio_ptr) { static unsigned int count = 0, bntd_flag = 0, bntu_flag = 0; int pending = 0; int reenable = 1; unsigned int reg; unsigned int value; // block on the file waiting for an interrupt */ read(fd, (void *)&pending, sizeof(int)); puts("IRQ"); // channel 1 reading value = reg_read(gpio_ptr, GPIO_DATA_OFFSET); if ((value & 0x00000001) != 0) { puts("sw12-1 on"); bntd_flag = 1; } 88 if ((bntd_flag == 1) && (value == 0)) { puts("sw12-1 off"); bntd_flag = 0; } if ((value & 0x00000002) != 0) { puts("sw12-2 on"); bntu_flag = 1; } if ((bntu_flag == 1) && (value == 0)) { puts("sw12-2 off"); bntu_flag = 0; } count++; usleep(50000); // anti rebond if(count == 10) flag_end = 1; // the interrupt occurred for the 1st GPIO channel so clear it reg = reg_read(gpio_ptr, GPIO_IRQ_STATUS); if (reg != 0) reg_write(gpio_ptr, GPIO_IRQ_STATUS, 1); // re-enable the interrupt in the interrupt controller thru the // the UIO subsystem now that it's been handled write(fd, (void *)&reenable, sizeof(int)); } int main() { int fd; int reenable = 1; unsigned long gpio_addr, gpio_size; unsigned int value; unsigned long page_addr, page_offset; void *ptr; void *ptr_axi_gpio; unsigned page_size=sysconf(_SC_PAGESIZE); /* Open /dev/mem file */ fd = open ("/dev/mem", O_RDWR); if (fd < 1) { printf("Unable to open /dev/mem\n"); return -1; } // // GPIO_APER enable // gpio_addr = 0xf800012c; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); 89 value = reg_read(ptr, page_offset); value = value | 0x00400000; // bit 22 = 1 reg_write(ptr, page_offset, value); munmap(ptr, page_size); close(fd); printf("GPIO APER enabled\n"); // // UIO configuration // /* Open the UIO device file */ fd = open("/dev/uio1", O_RDWR); if (fd < 1) { printf("Invalid UIO device file: /dev/uio1.\n"); return -1; } gpio_size = get_memory_size("/sys/class/uio/uio1/maps/map0/size"); /* mmap the UIO devices */ ptr_axi_gpio = mmap(NULL, gpio_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // // AXI GPIO configuration // // channel1 = input reg_write(ptr_axi_gpio, GPIO_TRI_OFFSET, 0xffffffff); // channel2 = output reg_write(ptr_axi_gpio, GPIO_TRI2_OFFSET, 0x00000000); // Global interrupt enable, Bit 31 = 1 reg_write(ptr_axi_gpio, GPIO_GLOBAL_IRQ, 0x80000000); // Channel 1 Interrupt enable reg_write(ptr_axi_gpio, GPIO_IRQ_CONTROL, 1); // enable the interrupt controller thru the UIO subsystem write(fd, (void *)&reenable, sizeof(int)); printf("AXI GPIO configured\n"); //wait for interrupt while (flag_end == 0) { wait_for_interrupt(fd, ptr_axi_gpio); } printf("End of AXI GPIO test\n"); munmap(ptr_axi_gpio, gpio_size); close(fd); printf("End of program.\n"); return 0; } 90 Ce programme contient les fonctions suivantes : void reg_write(void *reg_base, unsigned long offset, unsigned long value) Ecrit une valeur reg_base+offset unsigned long reg_read(void unsigned long offset) Lit une valeur 32 bits à l’adresse reg_base+offset *reg_base, int get_memory_size(char *sysfs_path_file) 32 bits à l’adresse Lit le range du contrôleur dans le fichier /sys/class/uio/uio1/maps/map0/size wait_for_interrupt(int fd, void *gpio_ptr) Cette fonction attend une interruption sur le contrôleur fd à l’adresse gpio_ptr, traite l’IT, l’efface puis revalide les interruptions. Avec UIO, le traitement des interruptions se déroule de la manière suivante : 1. Une lecture sur /dev/uiox est bloquante jusqu’à ce qu’une interruption se produise. 2. Une écriture sur /dev/uiox revalide les interruptions. Le résultat est le suivant : 91 La deuxième application, test_emio_uio, va tester la détection de l’interruption à l’aide des boutons SW5 et SW7 sur front montant. Le code C est le suivant : #include #include #include #include #include #include #define #define #define #define #define #define #define #define #define <stdio.h> <stdlib.h> <unistd.h> <sys/mman.h> <fcntl.h> <time.h> DATA_2_RO DIRM_2 OEN_2 INT_EN_2 INT_DIS_2 INT_STAT_2 INT_TYPE_2 INT_POLARITY_2 INT_ANY_2 0x0068 0x0284 0x0288 0x0290 0x0294 0x0298 0x029C 0x02A0 0x02A4 volatile static int flag_end = 0; inline void reg_write(void *reg_base, unsigned long offset, unsigned long value) { *((volatile unsigned long *)(reg_base + offset)) = value; } inline unsigned long reg_read(void *reg_base, unsigned long offset) { return *((volatile unsigned long *)(reg_base + offset)); } unsigned int get_memory_size(char *sysfs_path_file) { FILE *size_fp; unsigned int size; // open the file that describes the memory range size that is based on the // reg property of the node in the device tree size_fp = fopen(sysfs_path_file, "r"); if (size_fp == NULL) { printf("unable to open the uio size file\n"); exit(-1); } // get the size which is an ASCII string such as 0xXXXXXXXX and then be stop // using the file fscanf(size_fp, "0x%08X", &size); fclose(size_fp); return size; } void wait_for_interrupt(int fd, void *gpio_ptr) { static unsigned int count = 0; int pending = 0; int reenable = 1; 92 unsigned int value; // block on the file waiting for an interrupt */ read(fd, (void *)&pending, sizeof(int)); value = reg_read(gpio_ptr, INT_STAT_2); if ((value & 0x01 ) != 0) puts("SW5 activation"); if ((value & 0x02 ) != 0) puts("SW7 activation"); // clear the corresponding interruption if (value != 0) reg_write(gpio_ptr, INT_STAT_2, value); count++; usleep(50000); // anti rebond if(count == 6) flag_end = 1; // re-enable the interrupt in the interrupt controller thru the // the UIO subsystem now that it's been handled write(fd, (void *)&reenable, sizeof(int)); } int main() { int fd; int reenable = 1; unsigned long gpio_addr, gpio_size; unsigned int value; unsigned long page_addr, page_offset; void *ptr; void *ptr_emio; unsigned page_size=sysconf(_SC_PAGESIZE); /* Open /dev/mem file */ fd = open ("/dev/mem", O_RDWR); if (fd < 1) { printf("Unable to open /dev/mem\n"); return -1; } // // GPIO_APER enable // gpio_addr = 0xf800012c; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr); value = reg_read(ptr, page_offset); value = value | 0x00400000; // bit 22 = 1 93 reg_write(ptr, page_offset, value); munmap(ptr, page_size); close(fd); printf("GPIO APER enabled\n"); // // UIO configuration // /* Open the UIO device file */ fd = open("/dev/uio0", O_RDWR); if (fd < 1) { printf("Invalid UIO device file: /dev/uio0.\n"); return -1; } gpio_size = get_memory_size("/sys/class/uio/uio0/maps/map0/size"); /* mmap the UIO devices */ ptr_emio = mmap(NULL, gpio_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // // EMIO configuration // // pin direction reg_write(ptr_emio, DIRM_2, 0x0000003C); // output enabled reg_write(ptr_emio, OEN_2, 0x0000003C); // rising edge triggered IRQ on bit 0-1 reg_write(ptr_emio, INT_TYPE_2, 0x00000003); reg_write(ptr_emio, INT_POLARITY_2, 0x00000003); reg_write(ptr_emio, INT_ANY_2, 0x00000000); // IT enabled on bit 0-1 reg_write(ptr_emio, INT_EN_2, 0x00000003); // enable the interrupt controller thru the UIO subsystem write(fd, (void *)&reenable, sizeof(int)); printf("EMIO bank 2 configured\n"); //wait for interrupt while (flag_end == 0) { wait_for_interrupt(fd, ptr_emio); } printf("End of EMIO test\n"); munmap(ptr_emio, gpio_size); close(fd); printf("End of program.\n"); return 0; } 94 Le principe est le même que dans le programme précédent, mais la gestion des registres internes du contrôleur des EMIO de la bank 2 est un peu plus compliquée. Le résultat est le suivant : 95 96 3 ACQUISITION AVEC IP AXI LITE ET TRANSFERT EMIO 3.1 INTRODUCTION Le but de ce système de test est de simuler une acquisition en temps réel, de transférer les données sous Linux par les EMIO, puis de les évacuer via un programme client/serveur développé en langage C et utilisant les sockets vers un PC qui servira de machine de stockage. PL PS PC Génération de Sous Sous linux données petalinux Gbit ethernet EMIO UDP : données client socket serveur socket Le design dans la PL aura l’allure suivante : Bus AXI IP TEST 2 Registres écriture 2 Registres lecture Compteur 32 bits ÷ incrément 100 MHz PS EMIO 97 FIFO 64k x 32 Interface Disque dur Le design de test comporte un composant propriétaire IP_TEST qui n’utilisera qu’un registre en écriture (reg0) et un registre en lecture (reg2) au lieu des 4 prévus. Le compteur 32 bits s’incrémente au rythme du 100 MHz divisé par un incrément codé sur 8 bits (1 < incr < 255). Le débit des données 32 bits est donc compris entre : 1,57 Mo/s < Débit < 400 Mo/s La FIFO est nécessaire car la production des données se fait en temps réel et ne peut être arrêtée alors que le transfert va être réalisé par salve et donc avec un débit variable. On utilisera une liaison AXI Lite pour accéder aux registres et des EMIO pour transférer les données vers le PS par bloc de 1k x 32. Deux possibilités existent pour transférer les données vers le PS : 1) Les interruptions. Le composant IP_TEST utilise une interruption pour signaler qu’un bloc de données de 1k x 32 est prêt à être transféré. Le PS transfert alors les données dans la routine de traitement de l’interruption, puis se remet en attente. Ce principe fonctionne très bien en standalone mais pose des problèmes de fiabilité sous Petalinux en cas de débordement. Quand le débit devient trop élevé, certaines IT sont manquées et le GIC (General Interrupt Controler) plante le PS et il faut souvent redémarrer la carte. La gestion des interruptions avec UIO n’a pas l’air très robuste. 2) Le polling. En mode polling, dès qu’un bloc de 1k x 32 est prêt dans la FIFO, le transfert peut commencer. Un état de remplissage de la FIFO est lu dans reg2 pour vérifier la quantité de données 32 bits présente ainsi que l’absence de débordement. Les performances sont identiques en standalone par rapport au montage précédent, mais la robustesse est bien meilleure sous Petalinux et on peut beaucoup plus facilement détecter une perte de données (via le débordement de la FIFO) que dans le montage précédent. C’est la version polling que nous utiliserons. 3.2 CREATION DU COMPOSANT IP_TEST Nous allons commencer par créer le projet zc702_acq pour la ZC702. Dans Vivado 2015.1, la création et la gestion des IP AXI4 n’est pas très efficace (et je suis gentil). Le plus simple pour gérer un IP AXI4, c’est de créer un IP AXI4 temporaire avec les bons paramètres, de créer un projet Vivado classique, puis de copier les fichiers de l’IP temporaire dans le projet. C’est ce que nous allons faire maintenant. Depuis le projet zc702_acq, créons l’IP : 98 Cliquez sur « Next » : 99 Cliquez sur « Create a new AXI4 peripheral », puis sur « Next » : Laissez les paramètres par défaut et cliquez sur « Next » : 100 Sur l’interface S00_AXI, laissez 4 registres (c’est le minimum) et cliquez sur « Next » : Finalement, ajoutons l’IP au dossier de dépôt : 101 Dans le dossier hdl, nous trouvons les fichiers qui nous intéressent. Renommez myip_v1_0.vhd en ip_test.vhd et myip_v1_0_S00_AXI.vhd en ip_test_S00_AXI.vhd pour obtenir finalement : Fermez le projet zc702_acq puis créez dans /home/fpga/ip_repo un projet ip_test pour la ZC702. Copions-y les deux fichiers : 102 Puis : Puis : Puis : Finalement, vous devez obtenir : 103 Les fichiers sont alors copiés dans le dossier des importations : Nous allons ensuite créer un nouveau fichier my_logic.vhd : Dans la fenêtre « Define Module », on ne définit pas les entrées sorties et on clique sur « OK » : 104 Le fichier se trouvera dans le dossier des nouveaux fichiers hdl : Le rôle des fichiers est le suivant : ip_test.vhd C’est le top level. Il instancie les composants ip_test_S00_AXI et my_logic. Il récupère les signaux de commande (reset, valid, incr, …) depuis reg0. Il écrit le mot de sortie de my_logic (data32) dans reg2. ip_test_S00_AXI.vhd C’est le contrôleur du bus AXI Lite qui gère les 4 registres. Reg0 et reg1 sont en entrée, reg2 et reg3 sont en sortie. my_logic.vhd C’est notre logique qui gère les EMIO et contient un IP FIFO du catalogue. 105 Nous allons modifier ip_test.vhd afin d’obtenir : library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity ip_test is generic ( -- Users to add parameters here -- User parameters ends -- Do not modify the parameters beyond this line -- Parameters of Axi Slave Bus Interface S00_AXI C_S00_AXI_DATA_WIDTH : integer := 32; C_S00_AXI_ADDR_WIDTH : integer := 4 ); port ( -- Users to add ports here emio_toggle : in std_logic; emio_dout : out std_logic_vector(31 downto 0); -- User ports ends -- Do not modify the ports beyond this line -- Ports of Axi Slave Bus Interface S00_AXI s00_axi_aclk : in std_logic; s00_axi_aresetn : in std_logic; s00_axi_awaddr : in std_logic_vector(C_S00_AXI_ADDR_WIDTH-1 downto 0); s00_axi_awprot : in std_logic_vector(2 downto 0); s00_axi_awvalid : in std_logic; s00_axi_awready : out std_logic; s00_axi_wdata : in std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0); s00_axi_wstrb : in std_logic_vector((C_S00_AXI_DATA_WIDTH/8)-1 downto 0); s00_axi_wvalid : in std_logic; s00_axi_wready : out std_logic; s00_axi_bresp : out std_logic_vector(1 downto 0); s00_axi_bvalid : out std_logic; s00_axi_bready : in std_logic; s00_axi_araddr : in std_logic_vector(C_S00_AXI_ADDR_WIDTH-1 downto 0); s00_axi_arprot : in std_logic_vector(2 downto 0); s00_axi_arvalid : in std_logic; s00_axi_arready : out std_logic; s00_axi_rdata : out std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0); s00_axi_rresp : out std_logic_vector(1 downto 0); s00_axi_rvalid : out std_logic; s00_axi_rready : in std_logic ); end ip_test; architecture arch_imp of ip_test is component my_logic is port ( h100 sreset valid_cnt read_cnt incr data32 : : : : : : in std_logic; in std_logic; in std_logic; in std_logic; in std_logic_vector(7 downto 0); out std_logic_vector(31 downto 0); 106 -emio_toggle emio_dout : in std_logic; : out std_logic_vector(31 downto 0) ); end component my_logic; -- component declaration component ip_test_S00_AXI is generic ( C_S_AXI_DATA_WIDTH : integer := 32; C_S_AXI_ADDR_WIDTH : integer := 4 ); port ( reg0 : out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); reg1 : out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); reg2 : in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); reg3 : in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); S_AXI_ACLK : in std_logic; S_AXI_ARESETN : in std_logic; S_AXI_AWADDR : in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0); S_AXI_AWPROT : in std_logic_vector(2 downto 0); S_AXI_AWVALID : in std_logic; S_AXI_AWREADY : out std_logic; S_AXI_WDATA : in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); S_AXI_WSTRB : in std_logic_vector((C_S_AXI_DATA_WIDTH/8)-1 downto 0); S_AXI_WVALID : in std_logic; S_AXI_WREADY : out std_logic; S_AXI_BRESP : out std_logic_vector(1 downto 0); S_AXI_BVALID : out std_logic; S_AXI_BREADY : in std_logic; S_AXI_ARADDR : in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0); S_AXI_ARPROT : in std_logic_vector(2 downto 0); S_AXI_ARVALID : in std_logic; S_AXI_ARREADY : out std_logic; S_AXI_RDATA : out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); S_AXI_RRESP : out std_logic_vector(1 downto 0); S_AXI_RVALID : out std_logic; S_AXI_RREADY : in std_logic ); end component ip_test_S00_AXI; signal reg0, reg1, reg2, reg3 : std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0); signal reset, valid, read_reg2 : std_logic; signal incr : std_logic_vector(7 downto 0); begin -- Instantiation of Axi Bus Interface S00_AXI ip_test_S00_AXI_inst : ip_test_S00_AXI generic map ( C_S_AXI_DATA_WIDTH => C_S00_AXI_DATA_WIDTH, C_S_AXI_ADDR_WIDTH => C_S00_AXI_ADDR_WIDTH ) port map ( reg0 => reg0, reg1 => reg1, reg2 => reg2, reg3 => reg3, S_AXI_ACLK => s00_axi_aclk, S_AXI_ARESETN => s00_axi_aresetn, S_AXI_AWADDR => s00_axi_awaddr, 107 S_AXI_AWPROT S_AXI_AWVALID S_AXI_AWREADY S_AXI_WDATA => S_AXI_WSTRB => S_AXI_WVALID S_AXI_WREADY S_AXI_BRESP => S_AXI_BVALID S_AXI_BREADY S_AXI_ARADDR S_AXI_ARPROT S_AXI_ARVALID S_AXI_ARREADY S_AXI_RDATA => S_AXI_RRESP => S_AXI_RVALID S_AXI_RREADY => s00_axi_awprot, => s00_axi_awvalid, => s00_axi_awready, s00_axi_wdata, s00_axi_wstrb, => s00_axi_wvalid, => s00_axi_wready, s00_axi_bresp, => s00_axi_bvalid, => s00_axi_bready, => s00_axi_araddr, => s00_axi_arprot, => s00_axi_arvalid, => s00_axi_arready, s00_axi_rdata, s00_axi_rresp, => s00_axi_rvalid, => s00_axi_rready ); my_logic_inst : my_logic port map( h100 => s00_axi_aclk, sreset => reset, valid_cnt => valid, read_cnt => read_reg2, incr => incr, data32 => reg2, emio_toggle => emio_toggle, emio_dout => emio_dout ); process (s00_axi_aclk) begin if rising_edge(s00_axi_aclk) then reset <= reg0(0); valid <= reg0(1); read_reg2 <= reg0(2); if (reg0(15 downto 8) = x"00") then incr <= x"01"; else incr <= reg0(15 downto 8); end if; if (reset = '1') then reg3 <= x"00000000"; end if; end if; end process; end arch_imp; Dans le fichier ip_test_S00_AXI.vhd, les modifications suivantes doivent être apportées : • Changer le nom de l’entité et ajouter les 4 registres dans les entrées/sorties. library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; 108 entity ip_test_S00_AXI is generic ( -- Users to add parameters here -- User parameters ends -- Do not modify the parameters beyond this line -- Width of S_AXI data bus C_S_AXI_DATA_WIDTH : integer -- Width of S_AXI address bus C_S_AXI_ADDR_WIDTH : integer ); port ( -reg0 : reg1 : reg2 : reg3 : := 32; := 4 Users to add ports here out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); -- User ports ends -- Do not modify the ports beyond this line -- Global Clock Signal S_AXI_ACLK : in std_logic; -- Global Reset Signal. This Signal is Active LOW S_AXI_ARESETN : in std_logic; -- Write address (issued by master, acceped by Slave) S_AXI_AWADDR : in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0); -- Write channel Protection type. This signal indicates the -- privilege and security level of the transaction, and whether -- the transaction is a data access or an instruction access. S_AXI_AWPROT : in std_logic_vector(2 downto 0); -- Write address valid. This signal indicates that the master signaling -- valid write address and control information. S_AXI_AWVALID : in std_logic; -- Write address ready. This signal indicates that the slave is ready -- to accept an address and associated control signals. S_AXI_AWREADY : out std_logic; -- Write data (issued by master, acceped by Slave) S_AXI_WDATA : in std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); -- Write strobes. This signal indicates which byte lanes hold -- valid data. There is one write strobe bit for each eight -- bits of the write data bus. S_AXI_WSTRB : in std_logic_vector((C_S_AXI_DATA_WIDTH/8)-1 downto 0); -- Write valid. This signal indicates that valid write -- data and strobes are available. S_AXI_WVALID : in std_logic; -- Write ready. This signal indicates that the slave -- can accept the write data. S_AXI_WREADY : out std_logic; -- Write response. This signal indicates the status -- of the write transaction. S_AXI_BRESP : out std_logic_vector(1 downto 0); -- Write response valid. This signal indicates that the channel -- is signaling a valid write response. S_AXI_BVALID : out std_logic; -- Response ready. This signal indicates that the master -- can accept a write response. S_AXI_BREADY : in std_logic; -- Read address (issued by master, acceped by Slave) S_AXI_ARADDR : in std_logic_vector(C_S_AXI_ADDR_WIDTH-1 downto 0); 109 -- Protection type. This signal indicates the privilege -- and security level of the transaction, and whether the -- transaction is a data access or an instruction access. S_AXI_ARPROT : in std_logic_vector(2 downto 0); -- Read address valid. This signal indicates that the channel -- is signaling valid read address and control information. S_AXI_ARVALID : in std_logic; -- Read address ready. This signal indicates that the slave is -- ready to accept an address and associated control signals. S_AXI_ARREADY : out std_logic; -- Read data (issued by slave) S_AXI_RDATA : out std_logic_vector(C_S_AXI_DATA_WIDTH-1 downto 0); -- Read response. This signal indicates the status of the -- read transfer. S_AXI_RRESP : out std_logic_vector(1 downto 0); -- Read valid. This signal indicates that the channel is -- signaling the required read data. S_AXI_RVALID : out std_logic; -- Read ready. This signal indicates that the master can -- accept the read data and response information. S_AXI_RREADY : in std_logic ); end ip_test_S00_AXI; architecture arch_imp of ip_test_S00_AXI is -- AXI4LITE signals … • Commenter les lignes 217 et 218. • Commenter les lignes 244 et 252. 110 • Commenter les lignes 258 et 259. • Ajouter les lignes suivantes à la fin du fichier. Il reste à ajouter la FIFO dans le projet, puis à compléter my_logic.vhd. Nous allons utiliser une FIFO BlockRam avec horloge commune aux deux côtés. Sa taille sera de 64kx32 avec reset synchrone, sans status flag particulier. Il y aura par contre un comptage des données internes de la fifo (sur 16 bits). Les différents onglets se trouvent sur les pages suivantes. Nous allons consommer 58 BRAM sur 140 dans une ZC702. 111 112 113 Le code dans my_logic.vhd se trouve page suivante. Dans ce code, on attend d’avoir au moins 1024 valeurs 32 bits dans la FIFO avant de faire un transfert. On lit le contenu de la FIFO en changeant l’état du signal emio_toggle. A chaque transition 0→1 ou 1→0 sur l’entrée emio_toggle, une nouvelle valeur 32 bits sortant de la FIFO est copié sur la sortie emio_dout. La composition des registres 0 et 2 est la suivante : Reg0 Bit 0 : reset Bit 1 : validation compteur 32 bits Bit 2 : lecture registre 2 Bit 8 à 15 : incrément (le compteur avance au rythme du 100 MHz divisé par l’incrément) Reg2 Bit 0 à 15 : data_count de la FIFO Bit 16 : flag interne fifo full. Les 16 bits faibles de reg2 donnent le remplissage de la FIFO. De 0 à 65535, la fifo n’est pas pleine. A 65536, on déborde. Le débordement reste actif jusqu’au reset. 114 library ieee; use ieee.std_logic_1164.all; use IEEE.STD_LOGIC_unsigned.ALL; entity my_logic is port ( h100 sreset valid_cnt read_cnt incr data32 -emio_toggle emio_dout ); end my_logic; : : : : : : in std_logic; in std_logic; in std_logic; in std_logic; in std_logic_vector(7 downto 0); out std_logic_vector(31 downto 0); : in std_logic; : out std_logic_vector(31 downto 0) architecture arch_imp of my_logic is COMPONENT fifo_generator_0 PORT ( clk : IN STD_LOGIC; srst : IN STD_LOGIC; din : IN STD_LOGIC_VECTOR(31 DOWNTO 0); wr_en : IN STD_LOGIC; rd_en : IN STD_LOGIC; dout : OUT STD_LOGIC_VECTOR(31 DOWNTO 0); full : OUT STD_LOGIC; empty : OUT STD_LOGIC; data_count : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) ); END COMPONENT; signal signal signal signal signal begin cnt32, fifo_din, fifo_dout : std_logic_vector(31 downto 0); cnt_inc : std_logic_vector(7 downto 0); fifo_wr_en, fifo_rd_en, fifo_full, empty_fifo : std_logic; read_cnt_d1,flag_full,read_flag,emio_toggle_d1,emio_toggle_d2 : std_logic; data_count : std_logic_vector(15 downto 0); my_fifo_64kx32 : fifo_generator_0 PORT MAP ( clk => h100, srst => sreset, din => fifo_din, wr_en => fifo_wr_en, rd_en => fifo_rd_en, dout => fifo_dout, full => fifo_full, empty => empty_fifo, data_count => data_count ); emio_dout <= fifo_dout; fifo_rd_en <= emio_toggle_d2 xor emio_toggle_d1; process (h100) begin if rising_edge(h100) then if (sreset = '1') then data32 <= (others => '0'); 115 cnt32 <= (others => '0'); cnt_inc <= (others => '0'); fifo_din <= (others => '0'); fifo_wr_en <= '0'; read_cnt_d1 <= '0'; read_flag <= '0'; flag_full <= '0'; emio_toggle_d1 <= '0'; emio_toggle_d2 <= '0'; else if (valid_cnt = '1') then if (cnt_inc = incr-1) then cnt_inc <= (others => '0'); cnt32 <= cnt32 + 1; if (fifo_full = '0') then fifo_wr_en <= '1'; fifo_din <= cnt32; end if; else cnt_inc <= cnt_inc + 1; fifo_wr_en <= '0'; end if; else fifo_wr_en <= '0'; end if; if (fifo_full = '1') then flag_full <= '1'; end if; read_cnt_d1 <= read_cnt; -- status register reg0 bit 3 read_flag <= read_cnt and not read_cnt_d1; if (read_flag = '1') then data32 <= "000000000000000" & flag_full & data_count; end if; emio_toggle_d1 <= emio_toggle; -- emio sync with 111 MHz emio_toggle_d2 <= emio_toggle_d1; end if; end if; end process; end arch_imp; A la fin, vous devez obtenir la hiérarchie suivante : 116 Il reste à ajouter une contrainte de timing en créant un fichier ip_test.xdc et en copiant la ligne suivante dedans (la contrainte est sur une seule ligne, pas sur deux) : create_clock -period 10.000 -name s00_axi_aclk -waveform {0.000 5.000} [get_ports s00_axi_aclk] Le design s’implémente alors sans difficultés. La simulation nécessite une compréhension minimale du bus AXI Lite. Il faut d’abord créer le testbench ip_test_tb.vhd. En début de simulation, le bus AXI Lite doit être initialisé comme suit : S00_AXI_wdata <= x"00000000"; S00_AXI_araddr <= "0000"; S00_AXI_awvalid <= '0'; S00_AXI_bready <= '0'; S00_AXI_rready <= '0'; S00_AXI_wvalid <= '0'; 117 S00_AXI_arvalid <= '0'; emio_toggle <= '0'; wait for 100 ns; Pour réaliser une écriture dans un registre, la séquence suivante doit être respectée. L’adresse du registre S00_AXI_awaddr est un multiple de 4 (reg0 = 0x00, reg1 = 0x04, reg2 = 0x08, …). Le contenu du registre S00_AXI_wdata est codé sur 32 bits. S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000103"; -- écriture registre 0 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 500 ns; Le chronogramme est le suivant : Pour réaliser une lecture dans un registre, la séquence suivante doit être respectée. L’adresse du registre S00_AXI_araddr est un multiple de 4 (reg2 = 0x08, …). Le contenu du registre S00_AXI_rdata est codé sur 32 bits. 118 S00_AXI_rready <= '1'; -- lecture reg2 S00_AXI_araddr <= "1000"; wait for 20 ns; S00_AXI_arvalid <= '1'; wait for 20 ns; S00_AXI_arvalid <= '0'; wait for 20 ns; S00_AXI_rready <= '0'; wait for 100 ns; Le chronogramme est le suivant : Un exemple de testbench se trouve ci-dessous : library IEEE; use IEEE.Std_logic_1164.all; use IEEE.std_logic_unsigned.all; entity ip_test_tb is end; architecture bench of ip_test_tb is component ip_test generic ( C_S00_AXI_DATA_WIDTH : integer := 32; C_S00_AXI_ADDR_WIDTH : integer := 4 ); port ( emio_toggle : in std_logic; emio_dout : out std_logic_vector(31 downto 0); s00_axi_aclk : in std_logic; s00_axi_aresetn : in std_logic; s00_axi_awaddr : in std_logic_vector(C_S00_AXI_ADDR_WIDTH-1 downto 0); s00_axi_awprot : in std_logic_vector(2 downto 0); s00_axi_awvalid : in std_logic; 119 s00_axi_awready : out std_logic; s00_axi_wdata : in std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0); s00_axi_wstrb : in std_logic_vector((C_S00_AXI_DATA_WIDTH/8)-1 downto 0); s00_axi_wvalid : in std_logic; s00_axi_wready : out std_logic; s00_axi_bresp : out std_logic_vector(1 downto 0); s00_axi_bvalid : out std_logic; s00_axi_bready : in std_logic; s00_axi_araddr : in std_logic_vector(C_S00_AXI_ADDR_WIDTH-1 downto 0); s00_axi_arprot : in std_logic_vector(2 downto 0); s00_axi_arvalid : in std_logic; s00_axi_arready : out std_logic; s00_axi_rdata : out std_logic_vector(C_S00_AXI_DATA_WIDTH-1 downto 0); s00_axi_rresp : out std_logic_vector(1 downto 0); s00_axi_rvalid : out std_logic; s00_axi_rready : in std_logic ); end component; signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal signal emio_toggle: std_logic; emio_dout: std_logic_vector(31 downto 0); s00_axi_aclk: std_logic; s00_axi_aresetn: std_logic; s00_axi_awaddr: std_logic_vector(3 downto 0); s00_axi_awprot: std_logic_vector(2 downto 0); s00_axi_awvalid: std_logic; s00_axi_awready: std_logic; s00_axi_wdata: std_logic_vector(31 downto 0); s00_axi_wstrb: std_logic_vector(3 downto 0); s00_axi_wvalid: std_logic; s00_axi_wready: std_logic; s00_axi_bresp: std_logic_vector(1 downto 0); s00_axi_bvalid: std_logic; s00_axi_bready: std_logic; s00_axi_araddr: std_logic_vector(3 downto 0); s00_axi_arprot: std_logic_vector(2 downto 0); s00_axi_arvalid: std_logic; s00_axi_arready: std_logic; s00_axi_rdata: std_logic_vector(31 downto 0); s00_axi_rresp: std_logic_vector(1 downto 0); s00_axi_rvalid: std_logic; s00_axi_rready: std_logic ; constant clock_period: time := 10 ns; signal stop_the_clock: boolean; begin -- Insert values for generic parameters !! uut: ip_test generic map ( C_S00_AXI_DATA_WIDTH C_S00_AXI_ADDR_WIDTH port map ( emio_toggle emio_dout s00_axi_aclk s00_axi_aresetn s00_axi_awaddr s00_axi_awprot s00_axi_awvalid s00_axi_awready s00_axi_wdata s00_axi_wstrb s00_axi_wvalid s00_axi_wready s00_axi_bresp s00_axi_bvalid s00_axi_bready s00_axi_araddr s00_axi_arprot 120 => => => => => => => => => => => => => => => => => => => 32, 4) emio_toggle, emio_dout, s00_axi_aclk, s00_axi_aresetn, s00_axi_awaddr, s00_axi_awprot, s00_axi_awvalid, s00_axi_awready, s00_axi_wdata, s00_axi_wstrb, s00_axi_wvalid, s00_axi_wready, s00_axi_bresp, s00_axi_bvalid, s00_axi_bready, s00_axi_araddr, s00_axi_arprot, s00_axi_arvalid s00_axi_arready s00_axi_rdata s00_axi_rresp s00_axi_rvalid s00_axi_rready => => => => => => s00_axi_arvalid, s00_axi_arready, s00_axi_rdata, s00_axi_rresp, s00_axi_rvalid, s00_axi_rready ); H100 : process begin s00_axi_aclk <= '0'; wait for 5 ns; s00_axi_aclk <= '1'; wait for 5 ns; end process; sresetn_init : process begin S00_AXI_arprot <= "000"; S00_AXI_awprot <= "000"; S00_AXI_wstrb <= "1111"; s00_axi_aresetn <= '0'; wait for 10 ns; s00_axi_aresetn <= '1'; wait; end process; wreg0 : process begin S00_AXI_wdata <= x"00000000"; S00_AXI_araddr <= "0000"; S00_AXI_awvalid <= '0'; S00_AXI_bready <= '0'; S00_AXI_rready <= '0'; S00_AXI_wvalid <= '0'; S00_AXI_arvalid <= '0'; emio_toggle <= '0'; wait for 100 ns; S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000103"; -- reg0 : incr = 1, read_reg2 = 0, valid = 1, reset = 1 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 500 ns; S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000102"; -- reg0 : incr = 1, read_reg2 = 0, valid = 1, reset = 0 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 100 ns; 121 wait for 5000 ns; S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000100"; -- reg0 : incr = 1, read_reg2 = 0, valid = 0, reset = 0 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 100 ns; wait for 1000 ns; S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000403"; -- reg0 : incr = 4, read_reg2 = 0, valid = 1, reset = 1 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 100 ns; S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000402"; -- reg0 : incr = 4, read_reg2 = 0, valid = 1, reset = 0 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 100 ns; wait for 50 us; S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000406"; -- reg0 : incr = 4, read_reg2 = 1, valid = 1, reset = 0 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 100 ns; 122 S00_AXI_awaddr <= "0000"; S00_AXI_wdata <= x"00000402"; -- reg0 : incr = 4, read_reg2 = 0, valid = 1, reset = 0 S00_AXI_awvalid <= '1'; S00_AXI_bready <= '1'; S00_AXI_rready <= '1'; S00_AXI_wvalid <= '1'; wait for 20 ns; S00_AXI_wvalid <= '0'; S00_AXI_awvalid <= '0'; wait for 10 ns; S00_AXI_bready <= '0'; wait for 10 ns; S00_AXI_rready <= '0'; wait for 100 ns; S00_AXI_rready <= '1'; -- lecture reg2 S00_AXI_araddr <= "1000"; wait for 20 ns; S00_AXI_arvalid <= '1'; wait for 20 ns; S00_AXI_arvalid <= '0'; wait for 20 ns; S00_AXI_rready <= '0'; wait for 100 ns; wait for 1000 ns; emio_toggle <= '1'; -- lecture fifo wait for 100 ns; emio_toggle <= '0'; wait for 100 ns; emio_toggle <= '1'; wait for 100 ns; emio_toggle <= '0'; wait for 100 ns; emio_toggle <= '1'; wait for 100 ns; emio_toggle <= '0'; wait for 100 ns; emio_toggle <= '1'; wait for 100 ns; emio_toggle <= '0'; wait for 100 ns; emio_toggle <= '1'; wait for 100 ns; emio_toggle <= '0'; wait for 100 ns; wait; end process; kill : process begin wait for 60 us; assert (FALSE) report "fin de simulation." severity Failure; end process; end; Après simulation, le projet doit ressembler à ça : 123 Une fois que le design fonctionne correctement, il va falloir le transformer en IP AXI4 pour pouvoir l’utiliser dans zc702_acq. Pour cela, il faut cliquer sur : Puis packager le projet courant : 124 En incluant tous les fichiers de l’IP (indispensable en cas de présence d’un IP du catalogue) : Il reste à changer le nom de l’IP : Puis packager. 125 On peut noter l’apparition dans le projet d’un fichier XML nommé component.xml. C’est là la seule différence visible entre un projet Vivado normal et un IP. Ce fichier contient toutes les informations sur l’IP et sera utilisé dans le projet zc702_acq. En supprimant ce fichier et en repackageant, on réinitialise la configuration de l’IP. En double cliquant sur ce fichier, on ouvre automatiquement la fenêtre de packaging de l’IP. Ce fichier se trouve dans le dossier : L’IP est maintenant fini, passons au projet principal. Au préalable, nous pouvons faire le ménage dans ip_repo en gardant uniquement le composant ip_test et donc en supprimant myip_1.0. 3.3 CREATION DU DESIGN DE TEST ZC702_ACQ Ouvrons le projet zc702_acq créé précédemment et cliquons sur « Project Settings » : 126 Puis sur « IP » : Myip apparait en rouge car nous l’avons supprimé et nous ne voyons pas ip_test. Pour corriger tout cela, sélectionnez myip puis cliquez sur – pour le retirer du dépôt. Cliquez ensuite sur + et sélectionnez le dossier /home/fpga/ip_repo. Ip_test apparait maintenant. Fermez la fenêtre du « Project Settings ». 127 Nous pouvons maintenant passer au block design : • Créez un block Design appelé system et insérez dedans un Zynq7. • Puis cliquez sur « Run Block automation ». • Double cliquez ensuite sur le composant et ajoutez 33 EMIO : 128 • Réglez l’horloge clk0 à 100 MHz : • Ajoutez le composant ip_test dans le block design (BD) : • Puis cliquez sur « Run Connection automation ». Vous devez obtenir le schéma suivant : 129 • Il reste à connecter les EMIO. Ouvrez Le bus GPIO_0 en cliquant dessus : • Nous devons connecter GPIO_O(32) sur emio_toggle et GPIO_I(31 :0) sur emio_dout. Le tableau suivant vous donne la correspondance avec les EMIO : emio_dout EMIO 54-85 : bank 2 emio_toggle EMIO 86 : bit de poid faible de la bank 3 Insérez un composant slice dans le BD : Puis double-cliquez dessus. Le bus d’entrée fait 33 bits et on veut extraire le bit 32 : 130 • Il faut ensuite relier le bus GPIO_O sur l’entrée Din du slice, puis la sortie Dout du slice avec l’entrée emio_toggle du composant ip_test : • Nous pouvons ensuite relier la sortie emio_dout d’ip_test avec l’entrée GPIO_I du PS. Il y a évidemment un problème puisque emio_dout est sur 32 bits alors que GPIO_I est sur 33 bits. Dans ce cas, par défaut, Vivado connecte les 32 bits de emio_dout sur les poids faibles de GPIO_I, ce qui est bien ce que nous souhaitons. Finalement, vous devez obtenir le BD suivant. 131 Faites un « Validate Design » puis fermez et sauvez le BD. Il reste à générer les produits de sortie. Le message suivant confirme la différence de taille entre GPIO_I et emio_dout ainsi que le branchement sur les poids faibles de GPIO_I. Il faut ensuite créer le wrapper puis générer le bitstream. L’implementation doit se dérouler sans problème. 3.4 EN CAS DE MODIFICATION/ERREUR SUR LE COMPOSANT IP_TEST Il suffit d’ouvrir un des fichiers de l’IP (ici, ip_test.vhd) : 132 pour constater un point capital. Vivado travaille avec une copie locale du fichier dans le projet zc702_acq et pas sur l’IP lui-même qui se trouve dans le dossier ip_repo. Le fichier est d’ailleurs ouvert en lecture seule. Il ne sert à rien de modifier le projet IP_TEST si les nouveaux fichiers (mais aussi les anciens) ne sont pas recopiés dans le cache local du projet zc702_acq. La principale source d’erreurs avec un IP de ce type, c’est que tous les fichiers du projet IP ne soient pas copiés dans le projet qui l’utilise, notamment parce que le fichier component.xml n’est pas complet ou ne pointe pas sur les bons dossiers. C’est la principale faiblesse dans la gestion des IP avec Vivado. Par exemple, si les fichiers de l’IP FIFO n’étaient pas correctement copiés dans zc702_acq, un ? rouge apparaîtrait dans le symbole du composant. 133 Cela provoque une erreur à l’implémentation (mais seulement un warning en synthèse) qui signale globalement que le composant fifo_generator_0 n’a pas été trouvé. Comment corriger ce genre d’erreur ou bien comment répercuter les modifications dans l’IP ? La procédure suivante fonctionne la plupart du temps : 1) Fermez le projet zc702_acq et ouvrez le projet IP_TEST (dans ip_repo). Procédez aux éventuelles modifications dans le code VHDL. 2) Sélectionnez puis retirez du projet IP_TEST le fichier component.xml. 3) Recréez l’IP. En cochant bien l’option suivante : 134 4) N’oubliez pas de remettre le bon nom. Vous pouvez éventuellement changer le numéro de version. 5) Vous pouvez voir dans « File Groups » si la liste des fichiers est correcte. 6) Puis finalement cliquer sur « Package IP ». C’est là que les informations pertinentes sont copiées dans le fichier component.xml. 135 7) Fermez le projet IP_TEST et ouvrez le projet zc702_acq. Il faut maintenant répercuter les modifications dans le cache local. Elles sont parfois détectées automatiquement, mais pas toujours. Pour détecter le changement, il faut faire un « Report IP Status » : Le changement a dû être vu. Cliquez sur « Upgrade Selected » : 136 Le BD s’ouvre. Sur l’onglet « IP Status », faites un « Rerun ». Tout doit être à jour. 8) Faites un « Validate Design » puis fermez et sauvez le BD. 9) Sélectionnez le BD et faites un « Reset Output Products ». 10) Puis un « generate Ouput Products ». 11) L’IP FIFO doit apparaitre avec une petite clef à côté d’U0. La génération du bistream doit se dérouler correctement. 137 3.5 L’APPLICATION STANDALONE L’application standalone fonctionne en mode polling. Le code est le suivant : #include <stdio.h> #include <stdlib.h> #include "xil_io.h" #include "xil_exception.h" #include "xparameters.h" #include "xil_cache.h" #include "xil_printf.h" #include "xil_types.h" #include "sleep.h" #include "platform.h" #include "xgpiops.h" /* * Device hardware build related constants. */ #define MIO_10 10 #define MIO_86 86 #define IP_TEST_BASEADDR XPAR_IP_TEST_0_BASEADDR /************************** Function Prototypes ******************************/ int CheckData(u32 RxBufferPtr[], u32 len); int main() { int Status, Index, i; u32 reg0, reg2, lecture, valid, reset, incr, data_in; u32 RxBufferPtr[1024], ds23 = 0; XGpioPs_Config *ConfigPtrPS; XGpioPs mio_emio; init_platform(); xil_printf("test_acq : ip_test en polling\n"); /* configuration led 9 = MIO */ // verrouillage ressource matérielle 0 ConfigPtrPS = XGpioPs_LookupConfig(0); if (ConfigPtrPS == NULL) { return XST_FAILURE; } // initialisation MIO XGpioPs_CfgInitialize(&mio_emio, ConfigPtrPS, ConfigPtrPS->BaseAddr); // validation et direction MIO XGpioPs_SetDirectionPin(&mio_emio, MIO_10, 1); // led9, 1 = sortie XGpioPs_SetOutputEnablePin(&mio_emio, MIO_10, 1); XGpioPs_WritePin(&mio_emio, MIO_10, 0); // init = 0 // validation et direction EMIO pin 86 XGpioPs_SetDirectionPin(&mio_emio, MIO_86, 1); // emio_toggle, 1 = sortie XGpioPs_SetOutputEnablePin(&mio_emio, MIO_86, 1); XGpioPs_WritePin(&mio_emio, MIO_86, 0); // init = 0 // validation et direction EMIO bank 2 XGpioPs_SetDirection(&mio_emio, 2, 0x00000000); // bank2, 0 = entrée XGpioPs_SetOutputEnable(&mio_emio, 2, 0x00000000); reset = 0; 138 valid = 0; lecture = 0; incr = 42; /* reset IP_TEST */ reset = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); reset = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); /* internal 32bits value reading */ lecture = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); lecture = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); // read 32 bits counter value : FF & fifo filling (16 bits) reg2 = Xil_In32(IP_TEST_BASEADDR + 0x00000008); data_in = XGpioPs_Read(&mio_emio, 2); xil_printf("ip_test ready\n"); xil_printf("reg2 = %x\n", reg2); xil_printf("EMIO data = %x\n", data_in); xil_printf("\ngenerating data\n"); valid = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); for(i = 0; i < 10000; i++) { do { /* internal 32bits value reading */ lecture = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); lecture = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); // read 32 bits counter value : FF & fifo filling (16 bits) reg2 = Xil_In32(IP_TEST_BASEADDR + 0x00000008); } while(reg2 < 1024); // at least 1024 words 32 bits if (reg2 == 0x00010000) { // fifo full xil_printf("fifo full, exiting\n"); return -1; } for(Index = 1; Index < 1025; Index ++) { XGpioPs_WritePin(&mio_emio, MIO_86, Index%2); 139 RxBufferPtr[Index-1] = XGpioPs_Read(&mio_emio, 2); } Status = CheckData(RxBufferPtr, 1024); if (Status != XST_SUCCESS) { return XST_FAILURE; } if (i%500 == 0) { if (ds23 == 1) ds23 = 0; else ds23 = 1; } XGpioPs_WritePin(&mio_emio, MIO_10, ds23); } xil_printf("check data OK\n"); valid = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; Xil_Out32(IP_TEST_BASEADDR + 0x00000000, reg0); xil_printf("stop generating data\n"); xil_printf("fin test_acq\n"); return XST_SUCCESS; } int CheckData(u32 RxBufferPtr[], u32 len) { u32 Index; static u32 value = 0; for(Index = 0; Index < len; Index++) { if (RxBufferPtr[Index] != value) { xil_printf("Error : expected = 0x%X, received = 0x%X\n", value, (unsigned int)RxBufferPtr[Index]); return -1; } value++; } return XST_SUCCESS; } La led DS23 clignote durant l’acquisition. La fonction CheckData() permet de vérifier que le compteur 32 bits transféré s’incrémente correctement. Les performances suivantes (avec incrément minimum) ont été obtenues en transférant 1000000 blocs de 4 ko : En mode Debug Incrément = 42 soit 400/42 = 9.5 Mo/s En mode Release Incrément = 23 soit 400/23 = 17.4 Mo/s En mode Release sans CheckData() Incrément = 21 soit 400/21 = 19 Mo/s 140 Le résultat suivant doit s’afficher : 3.6 CREATION DU PROJET PETALINUX Pour créer le projet Petalinux, vous devez ouvrir un Terminal et aller dans le dossier du projet Vivado : cd /home/fpga/zc702_acq/ Lancez la commande (attention, ce sont des doubles tirets) : petalinux-create --type project --template zynq --name zc702_peta Il faut ensuite se placer dans le dossier du fichier HDF pour lancer la configuration : cd zc702_acq.sdk/ petalinux-config --get-hw-description -p ../zc702_peta L’outil de configuration du système Linux démarre. Allez dans « Subsystem AUTO Hardware Settings », « Ethernet Settings » et configurez le réseau comme sur l’image suivante : 141 Sortez et sauvez la configuration. Allons ensuite dans le dossier de petalinux : cd ../zc702_peta/ Activez ensuite TCF en allant dans : petalinux-config -c rootfs Filesystem Packages base tcf-agent Sortez en faisant Exit plusieurs fois puis sauvez la configuration. La commande : petalinux-config -c kernel permet la configuration du noyau (les drivers pour faire simple). Dans notre cas, il faut activer le mode debug du système de fichiers : Kernel hacking Compile-time checks and compiler options 142 Debug Filesystem Il faut aussi activer UIO (c’est normalement déjà le cas par défaut) : Device Drivers Userspace I/O drivers Userspace I/O platform driver with generic IRQ handling Sortez et sauvez la configuration. Il faut ensuite modifier le device tree en allant dans le dossier /home/fpga/zc702_acq/zc702_peta/subsystems/linux/configs/device-tree : Il faut ajouter la commande suivante dans le bootargs du fichier system-conf.dtsi : Pour activer UIO uio_pdrv_genirq.of_id=generic-uio afin d’obtenir : 143 Dans pl.dtsi, il faut changer le driver (generic-uio) de l’ip-test : Dans zynq-7000.dtsi, il faut changer le driver (generic-uio) ainsi que le label des EMIO : Il reste à lancer : petalinux-build Quand la génération de petalinux est terminée, allez dans : cd images/linux puis créez le fichier boot.bin : petalinux-package --boot --fsbl zynq_fsbl.elf --fpga system_wrapper.bit --uboot --force Copiez les fichiers image.ub et boot.bin sur la SDcard, puis bootez la ZC702. 144 Il faut vérifier que UIO0 correspond aux EMIO et que UIO1 correspond à IP_TEST. 3.7 DEVELOPPEMENT D’UNE APPLICATION LINUX DE TEST Nous allons refaire sous Linux le programme équivalent à celui que nous avons utilisé en standalone. Le programme de test est le suivant : #include #include #include #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <sys/mman.h> <fcntl.h> <time.h> <string.h> <sys/time.h> <signal.h> <pthread.h> <sys/socket.h> <sys/types.h> <arpa/inet.h> //inet_addr <errno.h> struct timeval tv1,tv2; struct timezone tz; inline void reg_write(void *reg_base, unsigned long offset, unsigned long value) { *((volatile unsigned long *)(reg_base + offset)) = value; } inline unsigned long reg_read(void *reg_base, unsigned long offset) { return *((volatile unsigned long *)(reg_base + offset)); } unsigned int get_memory_size(char *sysfs_path_file) { FILE *size_fp; unsigned int size; // open the file that describes the memory range size that is based on the // reg property of the node in the device tree size_fp = fopen(sysfs_path_file, "r"); if (size_fp == NULL) { printf("unable to open the uio size file\n"); 145 exit(-1); } // get the size which is an ASCII string such as 0xXXXXXXXX and then be stop // using the file fscanf(size_fp, "0x%08X", &size); fclose(size_fp); return size; } inline int test_fifo_full(void *ip_test_ptr, int incr) { unsigned int reg2, reg0; do { /* internal 32bits value reading */ reg0 = incr*256 + 1*4 + 1*2 + 0; reg_write(ip_test_ptr, 0x00000000, reg0); reg0 = incr*256 + 0*4 + 1*2 + 0; reg_write(ip_test_ptr, 0x00000000, reg0); // read 32 bits counter value : FF & fifo filling (16 bits) reg2 = reg_read(ip_test_ptr, 0x00000008); } while(reg2 < 1024); // at least 1024 words 32 bits if (reg2 == 0x00010000) // fifo full return 1; else return 0; } int CheckData(unsigned long RxBuffer[1024], int len) { unsigned long Index; static unsigned long counter = 0; for(Index = 0; Index < len; Index++) { if (RxBuffer[Index] != counter) { printf("Error : index = %d, expected = 0x%X, received = (int)Index, (unsigned int)counter, (unsigned int)RxBuffer[Index]); return -1; } counter++; } return 0; } int get_1024(void *emio_ptr, unsigned long RxBuffer[1024]) { unsigned int Index; for(Index = 1; Index < 1025; Index ++) { reg_write(emio_ptr, 0x004C, Index%2); RxBuffer[Index-1] = reg_read(emio_ptr, 0x00000068); } 146 0x%X\n", return 0; } int main (int argc, char *argv[]) { int fd_mem, fd_uio0, fd_uio1, i; unsigned long gpio_addr, ctrl_size0, ctrl_size1; unsigned int value; unsigned long page_addr, page_offset, status; unsigned long reg0, reg2, lecture, valid, reset, incr, RxBuffer[1024]; void *ptr, *emio_ptr; void *ip_test_ptr; unsigned page_size=sysconf(_SC_PAGESIZE); int diff; /* Open /dev/mem file */ fd_mem = open ("/dev/mem", O_RDWR); if (fd_mem < 1) { printf("Unable to open /dev/mem\n"); return -1; } // // GPIO_APER enable // gpio_addr = 0xf800012c; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_mem, page_addr); if (ptr == MAP_FAILED ) { printf("mmap() error on GPIO_APER\n"); return -1; } value = reg_read(ptr, page_offset); value = value | 0x00400000; // bit 22 = 1 reg_write(ptr, page_offset, value); munmap(ptr, page_size); printf("GPIO APER is enabled\n"); // // EMIO configuration // fd_uio0 = open("/dev/uio0", O_RDWR); if (fd_uio0 < 1) { printf("Invalid UIO device file: /dev/uio0.\n"); return -1; } ctrl_size0 = get_memory_size("/sys/class/uio/uio0/maps/map0/size"); /* mmap the UIO devices */ emio_ptr = mmap(NULL, ctrl_size0, PROT_READ|PROT_WRITE, MAP_SHARED, fd_uio0, 0); 147 // pin direction : bank0, bit 10 output value = reg_read(emio_ptr, 0x0204); value = value | 0x00000400; // bit 10 = 1 reg_write(emio_ptr, 0x0204, value); // output enable value = reg_read(emio_ptr, 0x0208); value = value | 0x00000400; // bit 10 = 1 reg_write(emio_ptr, 0x0208, value); printf("MIO10 is enabled\n"); // pin direction : bank3; bit 0 output value = reg_read(emio_ptr, 0x02C4); value = value | 0x00000001; // bit 0 = 1 reg_write(emio_ptr, 0x02C4, value); // output enable value = reg_read(emio_ptr, 0x02C8); value = value | 0x00000001; // bit 0 = 1 reg_write(emio_ptr, 0x02C8, value); printf("MIO86 is enabled\n"); // bank direction : bank2 input reg_write(emio_ptr, 0x0284, 0x00000000); // output disable reg_write(emio_ptr, 0x0288, 0x00000000); printf("bank 2 is enabled\n"); // // IP_TEST configuration // /* IP_TEST : Open the UIO device file */ fd_uio1 = open("/dev/uio1", O_RDWR); if (fd_uio1 < 1) { printf("Invalid UIO device file: /dev/uio1.\n"); return -1; } ctrl_size1 = get_memory_size("/sys/class/uio/uio1/maps/map0/size"); /* mmap the UIO devices */ ip_test_ptr = mmap(NULL, ctrl_size1, PROT_READ|PROT_WRITE, MAP_SHARED, fd_uio1, 0); /* write to register 0 */ reset = 0; valid = 0; lecture = 0; incr = 34; /* reset IP_GEN */ reset = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); 148 reset = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); printf("IP_TEST is reset\n"); /* internal 32bits value reading */ lecture = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); lecture = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); // read 32 bits counter value : FF & fifo filling (16 bits) reg2 = reg_read(ip_test_ptr, 0x00000008); value = reg_read(emio_ptr, 0x00000068); printf("reg2 = %x\n", (unsigned int)reg2); printf("EMIO data = %x\n", value); // start data generation valid = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); printf("\nGenerating data\n"); gettimeofday(&tv1, &tz); for (i = 0; i < 10000; i++) { status = test_fifo_full(ip_test_ptr, incr); // wait for 1024x32 bits if (status == 1) { printf("FIFO full, Exiting at iteration %d\n", i); return -1; } get_1024(emio_ptr, RxBuffer); status = CheckData(RxBuffer, 1024); if (status == -1) { return -1; } if (i%500 == 0) { value = reg_read(emio_ptr, 0x0040); value = value ^ (1<<10); reg_write(emio_ptr, 0x0040, value); } } // stop data generation valid = 0; reg0 = incr*256+ lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); printf("Stop generating data\n\n"); gettimeofday(&tv2, &tz); munmap(emio_ptr, ctrl_size0); 149 munmap(ip_test_ptr, ctrl_size1); close(fd_mem); close (fd_uio0); close (fd_uio1); diff=(tv2.tv_sec-tv1.tv_sec) * 1000000L + (tv2.tv_usec-tv1.tv_usec); printf("durée = %d usec\n",diff); printf("End of program.\n"); return 0; } Les résultats obtenus sont similaires à ceux obtenus avec l’application standalone : 3.8 MESURE DU TEMPS D’EXECUTION D’UNE FONCTION Pour pouvoir mettre au point un programme qui doit fonctionner en temps réel, il va falloir mesurer le temps de manière précise, typiquement à la µs près. Pour cela, nous allons utiliser la fonction gettimeofday() qui est supposée être précise à la µs près. Vérifions cela avec l’exemple suivant : #include <stdio.h> #include <unistd.h> #include <sys/time.h> struct timeval tv1,tv2; struct timezone tz; int main (int argc, char *argv[]) { int diff; gettimeofday(&tv1, &tz); usleep(1000); // attente 1000 usec gettimeofday(&tv2, &tz); diff=(tv2.tv_sec-tv1.tv_sec) * 1000000L + (tv2.tv_usec-tv1.tv_usec); printf("durée = %d usec\n",diff); return 0; } 150 tv1,tv2 et tz ont été déclarées en global de façon à pouvoir faire des mesures dans n’importe quelle fonction. Dans cet exemple, nous mesurons 1166 µs en mode Debug, ce qui est assez approximatif. L’erreur peut bien sûr provenir de la fonction usleep(), mais c’est impossible à dire dans ce cas. On constate toujours une erreur par excès de 100 à 200 µs quand on mesure des durées différentes. Après avoir réalisé une mesure matérielle non décrite dans ce document, j’ai pu constater que l’erreur de mesure avec gettimeofday() provient bien de la fonction usleep(). La méthode est donc suffisamment précise pour mesurer le temps. 3.9 PERFORMANCES Nous pouvons maintenant mesurer le temps d’exécution de chaque fonction dans la boucle de récupération de données et déterminer la valeur minimale de l’incrément. Les mesures suivantes ont été réalisées avec gettimeofday(). Le temps passé dans test_fifo_full() ne peut pas être mesuré facilement car il dépend du débit des données. En effet, c’est dans cette fonction que nous attendons d’avoir 1024 valeurs 32 bits pour commencer le transfert. La durée d’exécution de la fonction dépend donc de la valeur de l’incrément. On peut seulement estimer son temps minimum d’exécution si les données sont prêtes immédiatement : Debug release release 3 µs 1 µs 1 µs 350 µs 232 µs 232 µs 41 µs 7 µs Pas d’appel Total mesuré / débit maximum 347 µs / 11.8 Mo/s 239 µs / 17.1 Mo/s 233 µs / 59 Mo/s Incrément minimum théorique 34 24 23 temps minimum test_fifo_full() get_1024() CheckData() 151 3.10 DEVELOPPEMENT D’UNE APPLICATION SOCKET CLIENT/SERVEUR Le principe de notre design de test consiste en la génération à débit constant d’un compteur 32 bits (tous les 100MHz ÷ incrément) d’un côté de la FIFO et dans l’utilisation de ces données de l’autre côté de la FIFO à débit variable et en rafale. La taille de la FIFO, limitée par les ressources en blockRAM du FPGA de la ZC702, a été fixée à 256 ko, mais sa taille doit normalement être calculée en fonction du débit souhaité. L’aspect débit variable est notamment dû à l’évacuation des données vers le PC via le lien gigabit Ethernet. Pour établir le transfert entre le client sur la ZC702 et le serveur sur le PC, nous allons utiliser les sockets. Deux protocoles sont possibles : 1) TCP. Ce protocole établit une liaison fiable de bout en bout grâce à un mécanisme d’acquittement des datagrammes envoyés. On dit que l’on utilise le mode connecté. Si le serveur arrête d’envoyer les acquittements (parce qu’il écrit sur le disque dur par exemple), l’envoi des données côté client s’arrête aussi. La FIFO se remplit et peut déborder. Dans notre application, l’aspect fiabilité de la liaison peut poser problème. Quand un PC écrit sur disque dur, l’écriture s’arrête de temps en temps car le disque dur réalise diverses opérations telles que la recalibration thermique des têtes de lecture/écriture. L’écriture sur disque n’est donc pas continue et en cas d’attente trop longue, la fonction send() côté client se bloque par le biais des acquittements TCP. Des temps d’attente de plusieurs dizaines de msec sont fréquents et peuvent monter facilement jusqu’à une seconde. On pourrait réduire ce problème en utilisant un SSD, mais le principe de base est mauvais. Le transfert doit être effectué avec UDP. Les commandes échangées entre le client et le serveur peuvent par contre utiliser TCP. 2) UDP. Ce protocole se contente d’envoyer des datagrammes sans acquittement. On dit que l’on utilise le mode sans connexion. Le client envoie à son rythme des données dans le buffer de la socket UDP du serveur. Le serveur lit dans le buffer de sa socket et écrit les données (dans un fichier par exemple). En cas d’arrêt de l’écriture, le buffer se remplit. En dimensionnant correctement la socket (512 Mo par exemple), on peut compenser une grande durée d’interruption de la lecture de la socket UDP côté serveur (512 Mo à un débit de 12,5 Mo/s, cela fait 41 secondes d’arrêt). En théorie, des datagrammes peuvent être perdus, mais cela est peu probable en cas de liaison directe entre la carte ZC702 et le PC. 152 Le principe général des deux programmes est le suivant : main() main() Lecture de la socket UDP UDP par blocs 4 ko transfert EMIO de 4 et écriture des ko puis envoi par la données sur le disque socket UDP dur test_file.c Client sur la ZC702 Serveur sur le PC acq_udp_client.c acq_udp_server.c Fichiers de 1Mo Avant toute chose, nous allons configurer les sockets UDP et TCP sur le serveur Linux. Pour cela, il faut ajouter à la fin du fichier /etc/sysctl.conf les lignes suivantes : net.ipv4.tcp_window_scaling = 1 net.core.rmem_max = 1073741824 net.ipv4.tcp_rmem = 4096 1048576 1073741824 net.ipv4.tcp_wmem = 4096 16384 16777216 puis taper la commande : sudo sysctl –p /etc/sysctl.conf La taille maximale du buffer peut maintenant monter à 1 Go. Le programme serveur est le suivant : #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/socket.h> #include <sys/types.h> #include<arpa/inet.h> #include<unistd.h> #include <errno.h> 153 int main(int argc , char *argv[]) { int socket_in; struct sockaddr_in server, from; char file_name[256]; FILE *fp; int OptVal, i, size, cur_size, dec, n, idx_file, nb_file; unsigned int OptLen = sizeof(OptVal); char *data; socklen_t fromsize = sizeof(from); size = 4*1024; data = (char *)malloc(size); //Create socket socket_in = socket(AF_INET , SOCK_DGRAM , 0); if (socket_in == -1) { printf("Could not create socket"); } OptVal = 256*1024*1024; setsockopt(socket_in,SOL_SOCKET,SO_RCVBUF, &OptVal, OptLen); getsockopt(socket_in,SOL_SOCKET,SO_RCVBUF, &OptVal, &OptLen); printf("socket size = %d\n", OptVal); getsockopt(socket_in,SOL_SOCKET,SO_RCVTIMEO, &OptVal, &OptLen); printf("time out = %d\n", OptVal); // zero out the structure memset((char *) &server, 0, sizeof(server)); //Prepare the sockaddr_in structure server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = htons( 27000 ); //Bind if( bind(socket_in,(struct sockaddr *)&server , sizeof(server)) < 0) { puts("bind failed"); return 1; } puts("bind done"); nb_file = 100; for (idx_file = 0; idx_file < nb_file; idx_file++) { sprintf(file_name, "/home/fpga/ftp_data/data%d.bin", idx_file); if ((idx_file%50)==1) puts(file_name); fp = fopen(file_name, "wb"); for(i = 0; i < 256; i++) { cur_size = size; dec = 0; 154 do { if((n = recvfrom(socket_in, data + dec, cur_size, 0, (struct sockaddr *)&from, &fromsize)) < 0) { perror("recvfrom()"); exit(errno); } dec += n; cur_size = cur_size - n; } while(cur_size != 0); fwrite(data, 1, size, fp); } fclose(fp); } close(socket_in); puts("End of transmission"); return 0; } Le programme client est le suivant : #include #include #include #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <sys/mman.h> <fcntl.h> <time.h> <string.h> <sys/time.h> <signal.h> <pthread.h> <sys/socket.h> <sys/types.h> <arpa/inet.h> //inet_addr <errno.h> struct timeval tv1,tv2; struct timezone tz; inline void reg_write(void *reg_base, unsigned long offset, unsigned long value) { *((volatile unsigned long *)(reg_base + offset)) = value; } inline unsigned long reg_read(void *reg_base, unsigned long offset) { return *((volatile unsigned long *)(reg_base + offset)); } 155 unsigned int get_memory_size(char *sysfs_path_file) { FILE *size_fp; unsigned int size; // open the file that describes the memory range size that is based on the // reg property of the node in the device tree size_fp = fopen(sysfs_path_file, "r"); if (size_fp == NULL) { printf("unable to open the uio size file\n"); exit(-1); } // get the size which is an ASCII string such as 0xXXXXXXXX and then be stop // using the file fscanf(size_fp, "0x%08X", &size); fclose(size_fp); return size; } inline int test_fifo_full(void *ip_test_ptr, int incr) { unsigned int reg2, reg0; do { /* internal 32bits value reading */ reg0 = incr*256 + 1*4 + 1*2 + 0; reg_write(ip_test_ptr, 0x00000000, reg0); reg0 = incr*256 + 0*4 + 1*2 + 0; reg_write(ip_test_ptr, 0x00000000, reg0); // read 32 bits counter value : FF & fifo filling (16 bits) reg2 = reg_read(ip_test_ptr, 0x00000008); } while(reg2 < 1024); // at least 1024 words 32 bits if (reg2 == 0x00010000) // fifo full return 1; else return 0; } int CheckData(unsigned long RxBuffer[1024], int len) { unsigned long Index; static unsigned long counter = 0; for(Index = 0; Index < len; Index++) { if (RxBuffer[Index] != counter) { printf("Error : index = %d, expected = 0x%X, received = (int)Index, (unsigned int)counter, (unsigned int)RxBuffer[Index]); return -1; } counter++; } 156 0x%X\n", return 0; } int get_1024(void *emio_ptr, unsigned long RxBuffer[1024]) { unsigned int Index; for(Index = 1; Index < 1025; Index ++) { reg_write(emio_ptr, 0x004C, Index%2); RxBuffer[Index-1] = reg_read(emio_ptr, 0x00000068); } return 0; } int main (int argc, char *argv[]) { int fd_mem, fd_uio0, fd_uio1, i; unsigned long gpio_addr, ctrl_size0, ctrl_size1; unsigned int value; unsigned long page_addr, page_offset, status; unsigned long reg0, reg2, lecture, valid, reset, incr, RxBuffer[1024]; void *ptr, *emio_ptr; void *ip_test_ptr; unsigned page_size=sysconf(_SC_PAGESIZE); int diff; int socket_desc; struct sockaddr_in to; int n, nb_file; unsigned int m = sizeof(n); socklen_t tosize = sizeof(to); /* Open /dev/mem file */ fd_mem = open ("/dev/mem", O_RDWR); if (fd_mem < 1) { printf("Unable to open /dev/mem\n"); return -1; } // // GPIO_APER enable // gpio_addr = 0xf800012c; /* mmap the device into memory */ page_addr = (gpio_addr & (~(page_size-1))); page_offset = gpio_addr - page_addr; ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_mem, page_addr); if (ptr == MAP_FAILED ) { printf("mmap() error on GPIO_APER\n"); return -1; } value = reg_read(ptr, page_offset); value = value | 0x00400000; // bit 22 = 1 reg_write(ptr, page_offset, value); 157 munmap(ptr, page_size); printf("GPIO APER is enabled\n"); // // EMIO configuration // fd_uio0 = open("/dev/uio0", O_RDWR); if (fd_uio0 < 1) { printf("Invalid UIO device file: /dev/uio0.\n"); return -1; } ctrl_size0 = get_memory_size("/sys/class/uio/uio0/maps/map0/size"); /* mmap the UIO devices */ emio_ptr = mmap(NULL, ctrl_size0, PROT_READ|PROT_WRITE, MAP_SHARED, fd_uio0, 0); // pin direction : bank0, bit 10 output value = reg_read(emio_ptr, 0x0204); value = value | 0x00000400; // bit 10 = 1 reg_write(emio_ptr, 0x0204, value); // output enable value = reg_read(emio_ptr, 0x0208); value = value | 0x00000400; // bit 10 = 1 reg_write(emio_ptr, 0x0208, value); printf("MIO10 is enabled\n"); // pin direction : bank3; bit 0 output value = reg_read(emio_ptr, 0x02C4); value = value | 0x00000001; // bit 0 = 1 reg_write(emio_ptr, 0x02C4, value); // output enable value = reg_read(emio_ptr, 0x02C8); value = value | 0x00000001; // bit 0 = 1 reg_write(emio_ptr, 0x02C8, value); printf("MIO86 is enabled\n"); // bank direction : bank2 input reg_write(emio_ptr, 0x0284, 0x00000000); // output disable reg_write(emio_ptr, 0x0288, 0x00000000); printf("bank 2 is enabled\n"); // // IP_TEST configuration // /* IP_TEST : Open the UIO device file */ fd_uio1 = open("/dev/uio1", O_RDWR); if (fd_uio1 < 1) { 158 printf("Invalid UIO device file: /dev/uio1.\n"); return -1; } ctrl_size1 = get_memory_size("/sys/class/uio/uio1/maps/map0/size"); /* mmap the UIO devices */ ip_test_ptr = mmap(NULL, ctrl_size1, PROT_READ|PROT_WRITE, MAP_SHARED, fd_uio1, 0); /* write to register 0 */ reset = 0; valid = 0; lecture = 0; incr = 40; /* reset IP_GEN */ reset = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); reset = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); printf("IP_TEST is reset\n"); /* internal 32bits value reading */ lecture = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); lecture = 0; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); // read 32 bits counter value : FF & fifo filling (16 bits) reg2 = reg_read(ip_test_ptr, 0x00000008); value = reg_read(emio_ptr, 0x00000068); printf("reg2 = %x\n", (unsigned int)reg2); printf("EMIO data = %x\n", value); //Create socket socket_desc = socket(AF_INET, SOCK_DGRAM, 0); if (socket_desc == -1) { printf("Could not create socket"); } getsockopt(socket_desc,SOL_SOCKET,SO_RCVBUF,(void *)&n, &m); printf("socket size = %d\n", n); // zero out the structure memset((char *) &to, 0, sizeof(to)); to.sin_addr.s_addr = inet_addr("192.168.10.2"); to.sin_family = AF_INET; to.sin_port = htons( 27000 ); 159 // start data generation valid = 1; reg0 = incr*256 + lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); printf("\nGenerating data\n"); gettimeofday(&tv1, &tz); nb_file = 100; for (i = 0; i < nb_file*256; i++) { status = test_fifo_full(ip_test_ptr, incr); // wait for 1024x32 bits if (status == 1) { printf("FIFO full, Exiting at iteration %d\n", i); return -1; } get_1024(emio_ptr, RxBuffer); if(sendto(socket_desc, (char *)RxBuffer, 4096, 0, (struct sockaddr *)&to, tosize) < 0) { perror("sendto()"); exit(errno); } if (i%500 == 0) { value = reg_read(emio_ptr, 0x0040); value = value ^ (1<<10); reg_write(emio_ptr, 0x0040, value); } } // stop data generation valid = 0; reg0 = incr*256+ lecture*4 + valid*2 + reset; reg_write(ip_test_ptr, 0x00000000, reg0); printf("Stop generating data\n\n"); gettimeofday(&tv2, &tz); munmap(emio_ptr, ctrl_size0); munmap(ip_test_ptr, ctrl_size1); close(fd_mem); close (fd_uio0); close (fd_uio1); close(socket_desc); diff=(tv2.tv_sec-tv1.tv_sec) * 1000000L + (tv2.tv_usec-tv1.tv_usec); printf("durée = %d usec\n",diff); printf("End of program.\n"); return 0; } 160 Le programme testant le contenu des fichiers enregistrés dans /home/fpga/ftp_data est le suivant : #include #include #include #include #include #include <sys/types.h> <sys/stat.h> <time.h> <stdio.h> <stdlib.h> <unistd.h> int doesFileExist(const char *filename) { struct stat st; int result; result = stat(filename, &st); return result; } void CheckData(char *file_name) { static unsigned int count = 0; FILE *fp; unsigned int buffer[1024], i, nb; puts(file_name); fp = fopen(file_name, "rb"); while ((nb = fread(buffer, 4, 1024, fp)) == 1024) { for(i = 0; i < 1024; i++) { if (buffer[i] != count) { printf("Error %d : expected = %x, read = %x\n", (int)i, count, buffer[i]); exit(-1); } count++; } } fclose (fp); remove(file_name); } int main(int argc, char *argv[]) { char file_name_cur[256], file_name_next[256]; int cnt_file, cnt_test; cnt_file = 0; while(1) { sprintf(file_name_cur, "%s/data%d.bin", argv[1], cnt_file); sprintf(file_name_next, "%s/data%d.bin", argv[1], cnt_file+1); if (doesFileExist(file_name_cur) != 0) { printf("No file to test : %s", file_name_cur); return -1; } cnt_test = 0; 161 while((doesFileExist(file_name_next) != 0) ) { cnt_test++; if (cnt_test == 5) break; usleep(1000*1000); } if (cnt_test != 5) { CheckData(file_name_cur); cnt_file++; } else { CheckData(file_name_cur); printf("End of test\n"); return 0; } } return 0; } Il doit être lancé sur la ligne de commande en tapant la commande suivante. Le dossier /home/fpga/ftp_data doit être créé au préalable. ./test_file /home/fpga/ftp_data Il faut d’abord lancer le serveur dans un terminal côté PC, puis le client dans le SDK. Une fois l’acquisition lancée, il faut démarrer test_file dans un autre terminal côté PC : 162 Que pouvons-nous attendre côté performances ? La commande sendto() qui envoie 4 ko par UDP prend 38 usec, soit un débit égal à 108 Mo/s. Avec les mesures du §3.9, on obtient : release temps minimum test_fifo_full() get_1024() 1 µs 232 µs sendto() 38 µs Total / débit maximum 271 µs / 15.1 Mo/s Incrément minimum 27 soit 14.8 Mo/s Evidemment, il faut prendre en compte la variation du temps de transfert du bloc 4 ko via UDP car il n’est pas constant. Avec un incrément de 28, soit 14,3 Mo/s, on devrait être certain du bon fonctionnement. Un test de 2 heures a permis de constater un fonctionnement correct avec un incrément égal à 27, soit 100000 fichiers de 1 Mo créés. 163 164