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