Vous êtes sur la page 1sur 142

Le point d’arrêt apparait :

Cliquez ensuite sur le bouton Resume. La flèche bleue est passée de la ligne 12 à la ligne 15 :

Cliquez plusieurs fois sur le bouton « Step Over ». A chaque fois que la flèche bleue sort de la
ligne 15, un message devrait s’afficher dans la console, mais il y a un bug dans la console
Eclipse de Windows et rien ne s’affiche :

53
Le vidage du buffer de sortie ne se fait pas sur la console au fur et à mesure de l’exécution du
programme, mais seulement à la fin, quand il s’arrête. De plus, à cause d’un autre bug
d’Eclipse sous Windows en mode pas à pas (Debug), il faut toujours terminer un printf avec
un \n. En résumé :
1) Il faut mettre une instruction fflush(stdout); après chaque printf.
2) Il faut toujours terminer un printf avec un \n.

Arréter le programme avec un Terminate, puis corrigez le fichier source :

Le debugger fonctionne maintenant correctement et les valeurs s’affiche à chaque tour en


mode pas à pas. Vous pouvez visualiser le contenu des variables locales de votre programme
dans la fenêtre suivante :

54
Si vous souhaitez inspecter directement la mémoire du programme en cours, cliquez sur le
menu Windows, Show View, Memory :

La fenêtre Memory apparait en bas à droite. Cliquez sur le + :

Tapez le nom du tableau tab dans la fenêtre Monitor Memory :

55
Le contenu du tableau au format hexadécimal apparait :

tab est un tableau de double (64 bits) alors que la fenêtre Memory regroupe les valeurs par
bloc de 32 bits. Changeons la largeur des blocs. Faites un clic droit dans la fenêtre Memory et
sélectionnez « Format » :

Puis cliquez sur le champ « Column Size » et réglez-le sur 8 puis cliquez sur OK :

56
Le nouveau regroupement se fait par blocs de 64 bits :

2.1.4 Saisie de valeurs au clavier


Arrêtez le debug en cliquant sur le bouton Terminate, revenez en perspective C/C++ et
modifiez le code de la manière suivante :

Dans la console, le message apparait :

Cliquez dans la console et tapez une valeur entière inférieure à 10, 8 par exemple puis
appuyez sur la touche « Entrée » du clavier. Le programme s’exécute :

57
Si vous revenez dans le dossier soc que vous avez ouvert au début du TP, vous trouverez les
dossiers workspace, .metadata ainsi que le dossier essai1.

Vous en savez maintenant assez pour utiliser Eclipse. Fermez le SDK. Vous pouvez faire, à
titre de révision, les exercices suivants.

2.1.5 Exercice n°1


Ecrivez un programme qui affiche un triangle isocèle formé d’étoiles. La hauteur du triangle,
c’est-à-dire le nombre de lignes, sera fourni par l’utilisateur comme dans l’exemple ci-
dessous :
combien de lignes ? 5
*
***
*****
*******
*********

On s’arrangera pour que la première étoile de la dernière ligne du triangle se trouve sur le bord
gauche de l’écran. Vous utiliserez une fonction MontreLigne qui accepte comme paramètre un
entier et qui affiche les lignes d’étoiles.

58
2.1.6 Exercice n°2
Ecrivez un programme qui accepte une liste de nombres entiers en entrée (255 au maximum)
ainsi que leur nombre et qui affiche la même liste à l’envers. Exemple :
combien de nombres : 5
donnez 5 valeurs entieres : 10 20 30 40 50

Liste inversee : 50 40 30 20 10

Vous utiliserez une fonction inverser qui accepte comme paramètres un tableau d’entiers ainsi
que la taille de ce tableau et qui inverse les valeurs dans le tableau. Les entrées-sorties (saisie
des valeurs et affichage des résultats) seront effectuées dans la fonction main().

2.1.7 Exercice n°3


Ecrivez un programme qui accepte une liste de nombres entiers en entrée (255 au maximum)
ainsi que leur nombre et qui affiche la valeur minimale et la valeur maximale de la liste.
Exemple :
combien de nombres a trier : 6
donnez 6 valeurs entieres : 15 45 3 0 -12 51

valeur minimale : -12


valeur maximale : 51

Vous utiliserez une fonction cherche_minmax qui accepte comme paramètres un tableau
d’entiers, la taille de ce tableau et deux pointeurs sur entiers (*val_min et *val_max).
Cette fonction déterminera la valeur minimale et la valeur maximale du tableau. Les entrées-
sorties (saisie des valeurs et affichage des résultats) seront effectuées dans la fonction
main().

2.1.8 Exercice n°4


Ecrivez un programme qui accepte un nombre entier en entrée et qui réalise l’inversion des
chiffres de cet entier. Exemple :
Entrez un nombre : 12345
Nombre inversé : 54321

Vous utiliserez une fonction inversion qui accepte comme paramètres un pointeur sur un
entier et qui réalise l’inversion des chiffres de cet entier. Les entrées-sorties (saisie des
valeurs et affichage des résultats) seront effectuées dans la fonction main().

59
2.1.9 Exercice n°5
Ecrivez un programme qui accepte une chaîne de caractères (255 caractères maximum) en
entrée et qui affiche la même chaîne avec tous ses caractères inversés. Exemple :
Tapez une ligne de texte : ceci est un essai

Chaine inversee : iasse nu tse icec

Vous utiliserez une fonction inverser qui accepte comme paramètre une chaîne et qui
inverse les caractères contenus dans cette chaîne. Les entrées-sorties (saisie et affichage de
la chaîne) seront effectuées dans la fonction main().

60
3 Travail pratique n°8

3.1 PS standalone

3.1.1 Vivado

3.1.1.1 Création du projet

Dans ce TP, nous allons travailler avec deux outils : Vivado et le SDK. Lancez Vivado en

double cliquant sur l’icône qui se trouve sur le bureau. Après un long moment, une
fenêtre s’ouvre : cliquez sur « Create Project » :

Dans la fenêtre suivante, cliquez sur le bouton « Next » :

61
Dans la fenêtre qui s’ouvre alors, vérifiez que l’emplacement du projet est bien
C:\Users\soc, tapez le nom du projet tp8, puis cliquez sur Next :

Dans la fenêtre suivante, vérifiez que « RTL Project » est sélectionné, puis cliquez sur le
bouton « Next » :

62
Dans la fenêtre suivante, vérifiez que « VHDL » est le langage sélectionné, puis cliquez sur le
bouton « Next » :

Dans la fenêtre suivante, cliquez sur le bouton « Next » :

63
Dans la fenêtre suivante, cliquez sur le bouton « Boards » puis sur ZC702 et enfin sur
« Next » :

Vous devez voir la fenêtre suivante avant de cliquer sur « Finish » :

64
Vivado s’ouvre sur le projet créé. Voici les différentes zones de l’interface :
Navigateur Sources Visualisation

Propriétés 3.1.1.2 Création du block Design Console

Dans une première étape, nous allons configurer le système ARM ainsi que ses périphériques
en créant un Block Design :

Dans la fenêtre qui s’ouvre, nous allons laisser le nom design_1 ainsi que les options par
défaut. Cliquez sur OK :

65
La fenêtre de création du block Design apparait. Cliquez sur « Add IP » :

Dans la fenêtre de recherche de l’IP, tapez Zynq puis double-cliquez sur l’IP « ZYNQ7
Processing System » :

66
Le composant (l’IP) est inséré dans le diagramme. Cliquez sur « Run Block Automation » :

Dans la fenêtre qui s’ouvre, cliquez sur « OK » :

67
Le PS va être configuré avec les options par défaut correspondant à la carte ZC702 (interface
mémoire DDR3 + MIO + interfaces par défaut) :

Il faut encore assurer une liaison d’horloge entre FCLK_CLK0 et M_AXI_GP0_ACLK. Pour
cela, il faut sélectionner la sortie FCLK_CLK0 qui change de couleur et devient marron, puis
placer le curseur de la souris sur le trait à droite du nom. Un crayon apparait. Cliquez et tirer
un fil vers l’entrée M_AXI_GP0_ACLK puis relâcher :

A la fin, vous devez obtenir la figure suivante :

68
Si vous double-cliquez sur le composant Zynq, la fenêtre de configuration du PS s’ouvre. Pour
ce premier TP, nous n’allons rien changer aux paramètres par défaut. Cliquez sur « OK » :

Pour remettre le design en ordre, faites un clic droit sur l’IP Zynq puis cliquez sur
« Regenerate Layout » :

69
Nous pouvons maintenant vérifier la cohérence du Block Design en faisant un clic droit sur
l’IP puis en cliquant sur « Validate Design » :

70
Si vous avez suivi les étapes précédentes, vous devez obtenir la figure suivante. Cliquez sur
« OK » :

Pour fermer le Block Design, cliquez sur la croix en haut à droite de la fenêtre :

Cliquez sur « OK » dans la fenêtre de confirmation :

Puis sauvez le Block Design design_1 que vous venez de créer en cliquant sur « Yes » :

71
Il apparait maintenant dans le gestionnaire de projet (design_1.bd) :

3.1.1.3 Création du Wrapper

Sélectionnez le BD, faites un clic droit puis cliquez sur « Generate Output Products » :

Pour que Vivado génère les fichiers du PS, cliquez sur « Generate » :

72
La génération est assez longue (environ une minute pour ce tp) car il faut synthétiser chaque
élément du block design. La fenêtre suivante apparait (c’est la préparation) :

Cliquez sur OK lorsque la fenêtre suivante s’ouvre :

73
Vous voyez l’état de la syntèse en haut et à droite de la fenêtre Vivado :

Quand tout est fini, Ready doit s’afficher :

Vous pouvez aussi voir l’état d’avancement dans la fenêtre « Design Runs » ou dans la fenêtre
« Log » :

Sélectionnez à nouveau design_1, faites un clic droit puis cliquez sur « Create HDL
Wrapper » :

74
Vivado crée automatiquement le top level design en VHDL qui s’appelle
design_1_wrapper.vhd et dont le rôle est d’instancier le système processeur (PS). On
appelle ce fichier un wrapper en anglais. Deux options sont possibles :
• Vivado gère le wrapper et le modifie automatiquement à chaque changement du Block
Design. C’est l’option à utiliser si vous n’insérez pas votre code VHDL dans le wrapper.
• Vivado génère le wrapper la première fois puis en fait une copie et n’y touche plus. C’est
l’option à utiliser si vous insérez votre code VHDL dans le wrapper.

Dans ce TP, nous allons laisser Vivado gérer le wrapper :

Le wrapper est créé (attendez que updating arrête de s’afficher). Si vous cliquez sur la flèche >
devant son nom :
Arrêt Updating

Vous voyez apparaitre le Block Design instancié et ses composants internes :

75
Vous pouvez éditer le wrapper en double cliquant dessus. Il contient les entrées/sorties (E/S)
du PS ainsi que son instanciation.

Les E/S obligatoires du PS sont :


1. les MIO,
2. l’horloge 33.33 MHz et les PowerOnReset et SystemReset du PS,
3. les signaux de l’interface DDR3.

76
Ces E/S n’ont pas à être déclarées dans un fichier de contraintes XDC car il s’agit d’une
liaison directe avec le PS. Seules les E/S passant par la logique programmable PL doivent être
placées dans le fichier de contraintes. La création du top level design est obligatoire. C’est là
que nous placerons notre code VHDL dans les TP suivants. Comme il n’y a pas de PL dans
TP8, nous pouvons passer à la partie logicielle avec le SDK.

3.1.1.4 Préparation des fichiers du SDK

Cliquez sur le menu « File », puis sur « export », puis sur « Export Hardware… » :

Dans la fenêtre qui s’ouvre, cliquez sur « OK » :

77
Nous pouvons maintenant lancer le SDK. Cliquez sur le menu « File », puis sur « Launch
SDK » :

Dans la fenêtre qui s’ouvre, cliquez sur « OK » :

3.1.2 Software Develoment Kit

3.1.2.1 Création d’un projet helloworld

Le Software Development Kit (SDK) est lancé. Il faut attendre un moment (c’est normal, c’est
du Java) avant de voir apparaitre la fenêtre d’Eclipse. Tant que le disque dur travaille, ne
touchez pas au PC et attendez que la fenêtre suivante apparaisse :

78
Le fichier system.hdf, créé par Vivado lors de l’exportation, vous indique les adresses du
PS. Il est ouvert par défaut. Le fichier ps7_init.html contient le détail de la configuration
du PS (MIO, périphériques, processeur, DDR3, …). Vous pouvez l’ouvrir en double cliquant
dessus (attention, cela prend aussi du temps).

Fermez la fenêtre d’édition.

79
De nombreux périphériques sont activés par défaut. Nous n’utiliserons que l’UART dans ce
projet. Les autres périphériques auraient pu être désactivés dans la configuration de l’IP Zynq.
Les autres fichiers ps7_init.* permettent d’initialiser le PS (DDR3, horloge, PLL, MIO).
• Le fichier ps7_init.tcl peut être utilisé pour une initialisation manuelle du PS avec la
console Xilinx Microprocessor Debugger (XMD).
• Les fichiers ps7_init.c et ps7_init.h seront utilisés dans le projet logiciel pour
initialiser le PS.

Pour créer le projet logiciel, cliquez sur les menus « File », « New », « Application Project » :

Tapez le nom du projet tp8 dans le champ « Project name » puis cliquez sur Next. Nous allons
créer à cette étape le projet tp8 :
1. dans le workspace,
2. pour la plateforme hardware importée depuis Vivado (design_1_wrapper_hw_platform_0),
3. pour le cœur 0 du cortex A9 (ps7_cortexa9_0),
4. sans système d’exploitation (Operating System OS) mode standalone,
5. en C,
6. avec création du Board Support Package tp8_bsp.

80
Dans la fenêtre suivante, sélectionnez le template « Hello World » puis cliquez sur le bouton
« Finish » :

81
Le BSP et le projet sont créés, puis automatiquement compilés, ce qui peut prendre un certain
temps la première fois.

Les messages concernant la compilation s’affichent dans la console. Ne faites rien tant que le
message suivant n’est pas apparu dans la console pour signaler la fin de la compilation :

3.1.2.2 Les fichiers du projet

Deux nouveaux répertoires ont été créés : tp8 pour le projet et tp8_bsp pour le Board Support
Package. Le fichier system.mss contient des liens vers de la documentation et des exemples
d’utilisation des périphériques du PS. Il est ouvert par défaut lors de la création du BSP :

82
La fonction main() du projet se trouve dans le fichier helloworld.c. Double cliquez
dessus pour l’ouvrir. Les fichiers utilisés dans le projet se trouvent ici :

83
La fonction init_platform() se trouve dans platform.c.

84
Une des raisons de la complexité apparente du code C, c’est qu’il peut être utilisé pour trois
processeurs :
1. le PowerPC (PPC) : c’est le prédécesseur du cortex A9, il est abandonné depuis la Virtex4
FX.
2. Le Microblaze : c’est le processeur soft de Xilinx qui est réalisé avec de la logique
programmable. Nous ne l’utiliserons pas dans ce cours.
3. Le Cortex A9.

Le flot de développement est identique pour les trois processeurs. En réalité, pour le Cortex
A9, le fichier platform.c n’est utilisé que pour inclure les fichiers d’entête. Il y a deux types de
fichiers d’entêtes :
1. Les entêtes du C ANSI standard (pour processeur ARM) genre stdio.h qui se trouvent
dans les répertoires includes de tp8.
2. Les entêtes spécifiques Xilinx qui commencent tous par la lettre x comme le xparameters.h
ou le xil_cache.h. Ils se trouvent dans le répertoire include de tp8_bsp.

Entête ANSI C ARM

Entête spécifique
Xilinx x*.h

Nous pouvons éditer par exemple le fichier d’entête xparameters.h qui contient des
macros utiles pour piloter les périphériques. Prenons l’exemple de l’UART (Universal

85
Asynchronous Receiver/Transmitter, c’est le port série). Dans le fichier system.hdf, nous
trouvons son adresse :

Dans le fichier xparameters.h, nous trouvons les macros STDIN_BASEADDRESS et


STDOUT_BASEADDRESS qui permettent de l’utiliser.

La fonction print() utilisée dans helloworld.c est définie dans le BSP dans le
répertoire libsrc/standalone_v6_5 (print.c). Pour l’éditer, sélectionnez-la dans
helloworld.c, puis appuyez sur la touche F3 du clavier. Le fichier print.c s’ouvre alors :

86
vous trouverez un appel à outbyte() qui se trouve dans outbyte.c que vous pouvez
éditer et qui utilise aussi la macro STDOUT_BASEADDRESS.

XUartPs_SendByte() est définie dans le fichier xuartps_hw.c dans le répertoire libsrc/


uartps_v3_5 (xuartps_hw.c) qui fait appel à la fonction XUartPs_WriteReg() :

Définie elle-même (xuartps_hw.h) dans une macro.

87
Le code exécutable de la fonction de base Xil_Out32() se trouve dans la librairie libxil.a.

Voilà, c’est fini. La fonction print() de haut en bas. Facile non !

Avant de faire tourner le programme sur la maquette, regardons les fichiers sur le disque dur.
Dans le dossier tp8, nous trouvons tous les fichiers du projet. Deux répertoires nous
intéressent car ils contiennent les fichiers sources :
• tp8.srcs qui contient les sources Vivado (VHDL, bd, fichier de contrainte, IP).
• tp8.sdk qui contient les sources du SDK (.c, .h, …).

88
Par exemple, dans le répertoire tp8.srcs/sources_1/bd/design_1/hdl, on trouve
le wrapper géré par Vivado :

Dans le dossier tp8.sdk, nous trouvons les fichiers de la partie logicielle.

Le dossier .metadata indique qu’il s’agit bien d’un workspace Eclipse. Puis nous trouvons
les dossiers déjà vus dans le « Project Explorer » du SDK plus un dossier temporaire. En y
regardant d’un peu plus près, on constate que les fichiers et dossiers du dossier sdk sont
similaires à ce que l’on voit dans le « Project Explorer » du SDK. Il se contente donc de
répliquer la structure des fichiers du workspace (hormis les fichiers et dossiers dont le nom
commence par un .). Par exemple :

89
Explorateur SDK Fichiers sur le disque dur

3.1.2.3 Exécution du programme

Passons maintenant à l’exécution du programme. Connectez la maquette de la manière


suivante (voir image suivante) :
• Cordon USB UART sur J17 d’un côté, sur une prise USB du PC de l’autre.
• Cordon JTAG Digilent sur la carte d’un côté, sur une prise USB du PC de l’autre.
• Alimentation sur J60.
• Tous les switches de SW16 à gauche.

90
Port série (UART) Port JTAG

Marche/arrêt

Alimentation

Switches à gauche

Mettez la maquette en marche. Allez ensuite dans le SDK. Sélectionnez tp8, faites un clic
droit et sélectionnez « Run As », « Run Configurations… » :

91
Dans la fenêtre qui s’ouvre, sélectionnez « Xilinx C/C++ application (GDB) » puis clic droit
et « New » :

Dans la fenêtre de configuration, cliquez sur l’onglet « STDIO Connection » :

92
La liaison série sera connectée au port COM5 du PC, vitesse 115200 bauds :

Cliquez ensuite sur le bouton « Run ». Le programme se lance. C’est assez long la première
fois et vous pouvez voir l’avancement en bas à droite :

93
Hello World doit apparaitre dans la console en passant par le port série :

Rappelons la séquence de démarrage depuis le JTAG :

1. Initialise le CPU 0.
2. Détermine le mode de boot.
3. Active le DAP et la chaine JTAG.
4. Charge l’application logicielle
tp8.elf dans l’OCM via le JTAG.
5. Le CPU 0 démarre l’application
logicielle depuis l’OCM.

Modifions le programme en tapant :

Dès que vous l’aurez enregistré en cliquant sur , le SDK le recompile à la volée :

94
Vous pouvez alors le relancer en cliquant sur :

Le programme se lance et le nouveau message apparait dans la console :

Pour lancer le mode Debug, cliquez sur :

95
La perspective Debug s’ouvre (cliquez sur « Yes » dans la fenêtre de demande de
confirmation). Vous pouvez avancer en mode pas à pas dans le programme. N’oubliez pas de

cliquer sur « Terminate » pour fermer le programme. Fermez le SDK et Vivado puis
éteignez la maquette.

3.2 PS avec MIO connectés sur 2 switches et 2 leds

3.2.1 Introduction

Dans cette deuxième partie, nous allons créer un nouveau projet TP8_1 pour pouvoir
accéder aux 2 interrupteurs et aux 2 LEDs connectés directement sur le PS via les MIO. On
lira la valeur des interrupteurs SW15-1 et SW15-2 et on allumera les leds DS23 et DS12 via le
programme en C, sans utiliser de logique programmable. Le tableau suivant vous indique les
connexions à respecter :

MIO numéro Branché sur sens remarque

8 Led DS12 sortie Désactiver la QSPI

10 Led DS23 sortie

12 SW15-2 entrée SW14 branché en parallèle

14 SW15-1 entrée SW13 branché en parallèle

Dans un premier temps, allumez la maquette. Suivez ensuite les étapes vues dans tp8. Les
modifications suivantes doivent être apportées.

3.2.2 Modifications dans le Block Design


Avant de sauver le Block Design, double-cliquez sur l’IP Zynq. Dans la fenêtre de
configuration qui s’ouvre, sélectionnez « Peripheral I/O Pins puis décochez la case « Quad
SPI Flash » :

96
Cliquez sur le bouton « OK ». Avant de sauver le Block Design, vérifiez-le (F6).

3.2.3 Modifications dans le SDK


Dans le SDK, reprenez toutes les étapes vues dans TP8, y compris le lancement du projet
helloworld pour vérifier la connexion. Remplacez le code C de helloworld.c par le
code C suivant puis testez les 2 switches et les 2 leds de la maquette :

#include <stdio.h>
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"

#define MIO_8 8
#define MIO_10 10
#define MIO_12 12 Numéro des MIO
#define MIO_14 14

int main(void)
{
int i, Status;
XGpioPs_Config *ConfigPtrPS;
XGpioPs mio_emio;

xil_printf("essai MIO\n");

// verrouillage ressource matérielle 0


ConfigPtrPS = XGpioPs_LookupConfig(0);
if (ConfigPtrPS == NULL) {
return XST_FAILURE;

97
}

// initialisation MIO
XGpioPs_CfgInitialize(&mio_emio, ConfigPtrPS, ConfigPtrPS->BaseAddr);
// validation et direction MIO
XGpioPs_SetDirectionPin(&mio_emio, MIO_8, 1); // DS12 led, 1 = sortie
XGpioPs_SetOutputEnablePin(&mio_emio, MIO_8, 1);
XGpioPs_SetDirectionPin(&mio_emio, MIO_10, 1); // // DS23 led, 1 = sortie
XGpioPs_SetOutputEnablePin(&mio_emio, MIO_10, 1);

XGpioPs_SetDirectionPin(&mio_emio, MIO_12, 0); // SW15-2, 0 = entrée


XGpioPs_SetOutputEnablePin(&mio_emio, MIO_12, 0);
XGpioPs_SetDirectionPin(&mio_emio, MIO_14, 0); // SW15-1, 0 = entrée
XGpioPs_SetOutputEnablePin(&mio_emio, MIO_14, 0);

for (i = 0; i < 20; i++) {


Status = XGpioPs_ReadPin(&mio_emio, MIO_14); // lecture switches
xil_printf("switch SW15-1 = %d\n", Status);
Status = XGpioPs_ReadPin(&mio_emio, MIO_12);
xil_printf("switch SW15-2 = %d\n", Status);

XGpioPs_WritePin(&mio_emio, MIO_8, 0x0); // allumage leds


XGpioPs_WritePin(&mio_emio, MIO_10, 0x1);
usleep(500000);
XGpioPs_WritePin(&mio_emio, MIO_8, 0x1);
XGpioPs_WritePin(&mio_emio, MIO_10, 0x0);
usleep(500000);
}

XGpioPs_WritePin(&mio_emio, MIO_8, 0x1);


XGpioPs_WritePin(&mio_emio, MIO_10, 0x1);

return 0;
}

Quelques remarques et explications :

• Vous pouvez voir la définition de la fonction avec des explications succinctes en


sélectionnant la fonction puis en appuyant sur la touche F3 du clavier.

• Le numéro de la ressource matérielle utilisée dans l’appel ConfigPtrPS =

XGpioPs_LookupConfig(0); est défini dans le fichier xparameters.h

98
• Le schéma des MIO (et des EMIO) est le suivant :

O
Broche
PS

Pour mettre une MIO en sortie, il faut valider le buffer trois états (1 valide la sortie, 0 la
place dans l’état haute impédance)

XGpioPs_SetOutputEnablePin(&mio_emio, MIO_8, 1);

Et mettre la broche en sortie (1 = sortie, 0 = entrée)

XGpioPs_SetDirectionPin(&mio_emio, MIO_8, 1);

Pour mettre une MIO en entrée :

XGpioPs_SetOutputEnablePin(&mio_emio, MIO_12, 0);


XGpioPs_SetDirectionPin(&mio_emio, MIO_12, 0);

99
• La fonction printf() du C est fortement déconseillée car elle occupe beaucoup de place
en mémoire. La fonction xil_printf() à la même syntaxe et est beaucoup plus légère.
Sa seule restriction, c’est qu’elle ne traite pas les types float et double.
• Les MIO comme les EMIO apparaissent souvent sous le nom de GPIO ou bien de GpioPs
dans Vivado ou dans les programmes en C. GPIO signifie « General Purpose Input
Ouput ». Il s’agit d’un terme courant désignant des E/S d’usage général dans les systèmes
électroniques (et pas seulement chez Xilinx).

L’exécution de ce programme dure assez longtemps. Il n’est pas évident, en regardant la


console, de savoir si le programme tourne toujours ou s’il est arrêté. Pour en être sûr, il suffit
de regarder le carré rouge. C’est le bouton « Terminate ». S’il est rouge, c’est que le
programme est en cours d’exécution et vous pouvez l’arrêter en cliquant dessus. S’il est gris,
c’est que le programme est arrêté.

Fermez le SDK, Vivado puis éteignez la maquette.

3.3 Exercice

3.3.1 Introduction

Dans cette troisième partie, nous allons créer un nouveau projet TP8_2 pour pouvoir
accéder aux 2 boutons poussoirs et aux 8 LEDs connectés sur la PL via les EMIO. On lira la
valeur des boutons SW5 et SW7 et on allumera les leds DS15 à DS22 via le programme en C,
à travers la logique programmable.

Les MIO sont numérotées de 0 à 53 (bank 0 et 1), et les 64 EMIO (bank 2 et 3) se trouvent à
la suite de 54 à 117. Les EMIO peuvent être utilisées broche par broche comme dans ce TP,
ou bien bank 32 bits par bank 32 bits comme dans le TP n°9. Le tableau suivant vous indique
les connexions à respecter :

100
EMIO numéro Branché sur sens remarque
54 à 61 Leds DS15 à DS22 sortie Placer les broches dans le fichier de contraintes
62 Bouton SW5 entrée Placer la broche dans le fichier de contraintes
63 Bouton SW7 entrée Placer la broche dans le fichier de contraintes

Dans un premier temps, allumez la maquette. Suivez ensuite les étapes vues dans tp8. Les
modifications suivantes doivent être apportées.

3.3.2 Block Design


Dans le Block Design, double cliquez sur le composant Zynq. Dans la fenêtre de
configuration, sélectionnez « MIO Configuration », cliquez sur « I/O Peripherals » puis sur
« GPIO » :

101
Cochez la case « EMIO GPIO (Width) » puis sélectionnez une largeur égale à 10 :

Il nous faut 10 EMIO. Veillez à avoir cette fenêtre avant de cliquer sur « OK » :

Vous voyez apparaitre sur le composant Zynq un nouveau port nommé GPIO_0 :

Cliquez sur le signe + situé à droite du nom (une double flèche apparait) :

102
Vous voyez apparaitre les bus 10 bits constituant le port (Input, Output et Tristate)

Nous allons connecter GPIO_I et GPIO_O à des broches d’entrée-sorties. Pour cela, cliquez
sur le trait à droite de GPIO_I. La couleur du nom passe de noir à marron :

Faites un clic droit et cliquez sur « Make External » :

Recommencez avec GPIO_O. GPIO_T doit rester en l’air. Vous devez finalement obtenir la
figure suivante :

103
Pour remettre le design en ordre, faites un « Regenerate Layout » puis validez le design avec
« Validate Design ». Fermez et sauvez le Block Design puis générez les fichiers du PS avec
« Generate Output Products ».

3.3.3 Wrapper
Créez ensuite le wrapper. Lorsque la fenêtre suivante s’ouvre, sélectionnez le mode copie.

Le wrapper est maintenant créé dans un répertoire différent par rapport à TP8. Dans l’image
ci-dessous (tp8_2 en mode copie), le fichier a été copié dans le dossier imports/hdl. En
cas de modification du Block Design, il ne sera pas écrasé (sauf si vous le demandez).

104
Alors que dans tp8, le wrapper était dans le répertoire du block design bd/design_1/hdl.
En cas de modification du Block Design, il sera écrasé sans demande de confirmation.

Vous noterez que les GPIO apparaissent maintenant dans les E/S du composant design_1
(le composant design_1, c’est le PS). Les E/S du composant design_1 sont les E/S créées
dans le Block Design (DDR, FIXED_IO, GPIO_I, GPIO_O).

105
Ces E/S ont été automatiquement reliées aux E/S du top level design design_1_wrapper. Par
défaut, toutes les E/S du composant design_1 sont connectées aux E/S de
design_1_wrapper, ce qui n’est pas toujours correct. On verra dans TP9 qu’il est souvent
nécessaire de les supprimer.

Nous désirons réaliser le design suivant :

PS PL

10 GPIO_I
GPIO_I 2
switches

10 GPIO_O 8
GPIO_O
leds

106
Nous devons donc modifier le code VHDL afin d’obtenir (en gras les modifications) :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

library UNISIM;
use UNISIM.VCOMPONENTS.ALL;

entity design_1_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 );
FIXED_IO_ps_clk : inout STD_LOGIC;
FIXED_IO_ps_porb : inout STD_LOGIC;
FIXED_IO_ps_srstb : inout STD_LOGIC;
switches : in STD_LOGIC_VECTOR ( 1 downto 0 );
leds : out STD_LOGIC_VECTOR ( 7 downto 0 )
);
end design_1_wrapper;

architecture STRUCTURE of design_1_wrapper is

component design_1 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_0 : in STD_LOGIC_VECTOR ( 9 downto 0 );
GPIO_O_0 : out STD_LOGIC_VECTOR ( 9 downto 0 )
);
end component design_1;

107
signal gpio_i : std_logic_vector(9 downto 0);
signal gpio_o : std_logic_vector(9 downto 0);

begin
design_1_i: component design_1
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,
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_0(9 downto 0) => gpio_i,
GPIO_O_0(9 downto 0) => gpio_o
);

leds <= gpio_o(7 downto 0);


gpio_i(9 downto 8) <= switches;

end STRUCTURE;

Sauvez vos modifications en cliquant sur (l’étoile à coté du nom infdique que le fichier n’a
pas été sauvé) :

La syntaxe est vérifiée à la volée pendant la saisie. Sans erreur de syntaxe, un petit carré vert
s’affiche en haut et à droite de la fenêtre d’édition :

108
Simulons une erreur de syntaxe et retirons le ; à la fin de leds <= gpio_o(7 downto 0)
Le carré passe au jaune et un deuxième carré passe au jaune devant la ligne qui pose
problème, elle-même étant surlignée en jaune. Comme souvent en programmation, c’est la
ligne qui suit l’erreur qui est détectée.

Si vous passez la souris sur le carré, un message apparait qui vous précise la nature de l’erreur :

Etonnamment, c’est un « Warning » et non une « Error ». Corrigez le code VHDL puis sauvez
le fichier.

3.3.4 Fichier de contraintes


Il faut maintenant ajouter un fichier de contraintes au projet en faisant un clic droit dans la
fenêtre « Sources » :

109
Dans la fenêtre qui s’ouvre, sélectionnez « Add or Create constraints » puis cliquez sur
« Next » :

Dans la fenêtre suivante, cliquez sur le signe + puis sur « Create File… » :

110
Dans la fenêtre de création du fichier XDC (Xilinx Design Constraint), tapez tp8_2 dans le
champ « File Name » puis cliquez sur « OK » :

Vous devez obtenir la fenêtre suivante avant de cliquer sur « Finish » :

Le fichier de contraintes doit apparaitre dans le gestionnaire de projet :

111
Double-cliquez dessus pour l’ouvrir puis tapez le contenu suivant (voir documentation de la
ZC702) :
#connexion aux leds 15-16-17-18-22-21-20-19
set_property PACKAGE_PIN E15 [get_ports {leds[7]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[7]}]
set_property PACKAGE_PIN D15 [get_ports {leds[6]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[6]}]
set_property PACKAGE_PIN W17 [get_ports {leds[5]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[5]}]
set_property PACKAGE_PIN W5 [get_ports {leds[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[4]}]
set_property PACKAGE_PIN V7 [get_ports {leds[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[3]}]
set_property PACKAGE_PIN W10 [get_ports {leds[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[2]}]
set_property PACKAGE_PIN P18 [get_ports {leds[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[1]}]
set_property PACKAGE_PIN P17 [get_ports {leds[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[0]}]
#
# connexion aux boutons poussoir SW5 et SW7
set_property PACKAGE_PIN G19 [get_ports {switches[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {switches[0]}]
set_property PACKAGE_PIN F19 [get_ports {switches[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {switches[1]}]

3.3.5 Bitstream
Quand tout est prêt, il faut synthétiser (1), implémenter le design (2), puis créer le fichier .bit (3).

112
Vous pouvez aussi directement commencer à l’étape (3). Toutes les étapes seront alors
réalisées automatiquement. Après génération du bitstream (2 minutes pour produire le fichier
.bit), cliquez sur « Open Hardware Manager » puis sur « OK » dans la fenêtre suivante :

Le gestionnaire du matériel s’ouvre. Il n’est pas connecté à la carte.

Cliquez sur « Open Target », puis sur « Auto Connect » :

113
La carte ZC702 et son FPGA XC7Z020 doivent être reconnus. Sélectionnez le FPGA, puis
faites un clic droit et cliquez sur « Program Device… » :

Dans la fenêtre qui s’ouvre, cliquez sur « Program » :

114
Le FPGA est maintenant programmé :

La led « DONE » DS3 doit être allumée sur la carte. C’est un moyen simple pour s’assurer
que le bitstream a bien été téléchargé dans le FPGA.

Vous pouvez maintenant fermer le Hardware Manager en cliquant sur la croix :

115
Cliquez sur « OK » :

Nous pouvons exporter le projet vers le SDK. Cliquez sur le menu « File », puis sur
« export », puis sur « Export Hardware… ». Dans la fenêtre qui s’ouvre, cochez la case
« Include Bitstream » puis cliquez sur « OK » :

A chaque fois qu’il y aura un bitstream dans le projet, il faudra cocher cette case.
Ouvrez le SDK.

3.3.6 SDK
Dans le SDK, reprenez toutes les étapes vues dans TP8, y compris le lancement du projet
helloworld pour vérifier la connexion. Remplacez ensuite le code C de helloworld.c
par votre code C en vous inspirant du projet TP8_1.

Dans une première boucle d’environ 10 secondes, vous allez tester l’état des boutons poussoir
SW5 et SW7.

Dans une deuxième boucle de durée suffisante, vous allez faire compter les leds DS15 à DS22
en binaire sur 8 bits (0 -> 255, puis retour à 0 et on recommence).

116
4 Travail pratique n°9

4.1 PS avec PL, EMIO et compteur 32 bits (méthodologie)

4.1.1 Introduction
Dans ce TP, nous allons utiliser le PS avec la PL, les EMIO ainsi qu’un compteur 32 bits en
VHDL. Nous allons voir une méthodologie nous permettant de simuler simplement notre code
VHDL relié avec le PS. Nous souhaitons réaliser le design suivant. Le PS fournit à la PL une
horloge 100 MHz CLK0 ainsi qu’un signal reset et CE. Il lit la valeur sur 32 bits du compteur.

PS PL

32
GPIO_I Compteur 32 bits

Reset CE

GPIO_O
2
CLK0

Nous allons utiliser la bank 2 des EMIO (32 bits) en entrée plus 2 EMIO supplémentaires en
sortie. Le tableau suivant vous indique les connexions à respecter :

EMIO numéro Branché sur sens remarque

54 à 85 Sortie compteur entrée Une bank 32 bits

86 reset sortie Durée > une période d’horloge

87 CE sortie Durée > une période d’horloge

Créez un nouveau projet TP9 puis éteignez et rallumez la maquette. Suivez ensuite les
étapes vues dans tp8. Les modifications suivantes doivent être apportées.

117
4.1.2 Block Design
- Ajoutez le composant Zynq, puis faites un « Run Block Automation ».
- Double cliquez sur le composant Zynq, puis cliquez sur « MIO Configuration », « I/O
Peripherals », « GPIO » et ajoutez 34 EMIO :

- Cliquez sur « Clock Configuration », « PL Fabric Clocks » et réglez la fréquence de clk0 à


100 MHz :

- Cliquez sur « OK » pour revenir au Block Design.


- Connectez les EMIO en externe.
- Cliquez sur la sortie FCLK_CLK0, faites un clic droit puis cliquez sur « Make External » :

118
- Reliez ensuite FCLK_CLK0 avec M_AXI_GP0_ACLK comme dans tp8. Vous devez
obtenir finalement le block design suivant :

- Faites un « Validate Design », un « Regenerate Layout », puis fermez en sauvant.


- Générez les fichiers du PS avec « Generate Output Products ».
- Créez ensuite le wrapper en sélectionnant le mode copie.

4.1.3 Wrapper
Dans le wrapper design_1_wrapper.vhd, créez les 3 signaux suivants :

119
Connectez-les ici :

Puis supprimez les trois lignes suivantes dans l’entité (attention, pas de point-virgule sur la
dernière ligne) :

Afin d’obtenir les E/S suivantes. Sauvez le wrapper et vérifiez l’absence d’erreurs.

120
Pour changer les propriétés d’un fichier, il faut le sélectionner dans la fenêtre « Sources », puis
modifier ses propriétés dans la fenêtre « Source File Properties ». Dans ce TP, nous allons
associer ce fichier uniquement avec la synthèse en décochant la case suivante :

121
4.1.4 Modèle de simulation
Arrivé à ce point, nous avons un problème : c’est la simulation. Nous aimerions rajouter dans
le wrapper notre code VHDL, faire un testbench et vérifier le bon fonctionnement de notre
code avec le simulateur. Or, les entrées sorties de notre design (gpio_i, gpio_o et clk100) sont
fournies ou lues par le PS, c’est-à-dire par le composant design_1 qui est instancié dans le
wrapper (et correspond au block design). Bien sûr, nous n’avons pas de modèle de simulation
du PS et encore moins du programme en C qu’il va exécuter. Comment allons-nous résoudre
ce problème ? Il existe plusieurs méthodes pour sortir de ce mauvais pas. Nous allons utiliser
la plus simple : créer un fichier uniquement pour la simulation avec gpio_i, gpio_o et clk100
comme entrées sorties et simuler (de manière simplifiée) le comportement du PS dans le
testbench. Ajoutez une nouvelle source dans le projet (« Add Sources ») de type simulation :

Et créez un nouveau fichier :

122
Que nous allons nommer tp9_simul.vhd :

Dans la fenêtre qui s’ouvre alors, ne changez rien et cliquez sur « Finish » :

Dans la fenêtre qui s’ouvre alors, ne changez rien et cliquez sur « OK » :

123
Puis sur « Yes » :

Le nouveau fichier apparait dans la fenêtre « Sources » dans les sources de simulation :

Nous allons associer ce fichier uniquement avec la simulation en décochant la case suivante :

124
Complétons maintenant notre design. Pour pouvoir incrémenter le compteur dans le design, il
faut ajouter le package :

Passons aux entrées sorties. Il faut inverser leur sens par rapport à celui du PS :
- gpio_i est une entrée du PS et donc une sortie de la logique programmable (PL).
- gpio_o est une sortie du PS et donc une entrée de la PL.
- clk100 est une sortie du PS et donc une entrée de la PL.

L’entité de tp9_simul.vhd doit donc être :

Nous pouvons maintenant ajouter notre code VHDL. Nous allons avoir besoin des signaux
suivants :

125
Et du code suivant :

Sauvez le fichier et vérifiez l’absence d’erreurs de syntaxe.

4.1.5 Testbench.
Ajoutez une nouvelle source de simulation et créez un nouveau fichier tp9_simul_tb :

Le testbench apparait dans la fenêtre « Sources » :

126
Pour faciliter la suite des opérations, vous devez connaitre quelques raccourcis clavier.
1. Il faut commencer par cliquer dans la fenêtre d’édition pour l’activer.
2. Ctrl+a (appui sur la touche Ctrl et sur la touche a en même temps) permet de sélectionner
tout le texte dans l’éditeur.
3. Ctrl+c permet de copier le texte sélectionné.
4. Ctrl+v permet de coller le texte copié précédemment dans un éditeur.
5. Ctrl+x permet de supprimer le texte sélectionné.

Pour générer automatiquement le squelette du testbench, nous allons utiliser un script PERL
qui se trouve sur le site de Doulos aux USA. Ouvrez Chrome et faites la recherche suivante,
puis cliquez sur le lien :

Sur la page de Doulos, cliquez dans la fenêtre d’édition, puis effacez tout (Ctrl+a, Ctrl+x) :

127
Cliquez dans la fenêtre du testbench tp9_simul_tb.vhd dans Vivado, sélectionnez tout le texte
(Ctrl+a), puis supprimez-le (Ctrl+x).

Cliquez dans la fenêtre tp9_simul.vhd de l’éditeur de Vivado :

128
Sélectionnez tout le texte (Ctrl+a), puis copiez-le (Ctrl+c). Sur le site de Doulos, cliquez dans
la fenêtre d’édition puis collez le contenu (Ctrl+v). Cliquez sur le bouton « Generate VHDL
Testbench.

Une nouvelle fenêtre web apparait avec le squelette du testbench. Sélectionnez avec la souris
les lignes suivantes, puis copiez-les :

129
Cliquez dans la fenêtre du testbench tp9_simul_tb.vhd dans Vivado, puis collez le texte du
testbench généré sur le site de Doulos (Ctrl+v)

Sauvez le testbench. Dans la fenêtre « Sources », vous devez voir la hiérarchie tp9_simul_tb
→ tp9_simul (après Updating) :

Comme design_1 ne doit pas être simulé, on peut le retirer de la simulation en décochant :

130
Complétons le testbench. Nous allons faire changer le reset et la validation une seule fois :

131
Sauvez le fichier et vérifiez l’absence d’erreurs de syntaxe. Dans les réglages de la simulation,
mettez une durée de 5000 ns en cliquant sur « Settings » :

Dans la fenêtre qui s’ouvre, cliquez sur l’onglet « Simulation » puis tapez 5000 ns dans le
champ xsim.simulate.runtime :

132
4.1.6 Simulation
Lancez la simulation fonctionnelle en cliquant sur « Run Simulation » puis sur « Run
Behavioral Simulation » :

La fenêtre du simulateur interne de Vivado, Xsim, apparait :

Dans la fenêtre où apparaissent les chronogrammes (fenêtre Wave), des résultats apparaissent,
mais nous n’observons que les signaux du testbench. Pour observer les signaux dans
tp9_simul, il faut cliquer dans la fenêtre Wave, sélectionner tous les signaux (Ctrl+a) :

Puis les supprimer (Ctrl+x) :

133
Cliquez ensuite sur uut pour faire apparaitre ses signaux internes :

Sélectionnez en un, puis Ctrl+a pour tous les sélectionner :

134
Faites un clic droit et cliquez sur « Add To Wave Window » :

Les signaux internes de tp9_simul apparaissent maintenant dans la fenêtre Wave :

Fermez la fenêtre du simulateur en cliquant sur la croix :

135
Dans la fenêtre qui s’ouvre, cliquez sur « OK » :

Dans la fenêtre qui s’ouvre alors, cliquez sur « Save » :

Dans la fenêtre qui s’ouvre, cliquez sur « save » :

Dans la dernière fenêtre, cliquez sur « Yes » pour ajouter la configuration de la fenêtre Wave
au projet :

136
Elle apparait ensuite dans la fenêtre « Sources » :

Vous pouvez relancer la simulation. Pour bien voir les chronogrammes, nous allons détacher
la fenêtre Wave de Vivado en cliquant sur :

Maximisez la taille de la fenêtre en cliquant sur puis cliquez sur l’icône « Zoom Fit » dans
la barre d’outils latérale :

Nous voyons maintenant les signaux internes de tp9_simul. Pour changer la base d’un
std_logic_vector (le radix), il faut sélectionner le signal, faire un clic droit puis :

137
Pour zoomer sur une portion des chronogrammes, il faut sélectionner de gauche à droite la
zone à zoomer avec la souris :

138
puis relâcher. Pour dézoomer, il faut sélectionner de droite à gauche avec la souris puis
relâcher :

Pour faire apparaitre un premier marqueur temporel jaune, il faut faire un clic droit sur les
chronogrammes. Pour en avoir un second, il faut cliquer sur le bouton « Add Marker » :

Un deuxième marqueur bleu apparait. Les étiquettes supérieures indiquent le temps absolu de
chaque marqueur, l’étiquette inférieure indique la différence temporelle entre les deux
curseurs :

139
Un signal de type std_logic_vector (ou integer) peut apparaitre sous forme analogique. Pour
cela, il faut le sélectionner, cliquer droit puis :

Le signal apparait sous forme continue. On ne l’utilise que si cela a un sens, comme par
exemple pour un signal sinusoidal. Sinon, on reste en numérique, comme dans ce TP.

140
Au départ de la simulation, le compteur est dans un état inconnu. Le reset (comme la
validation) est réalisé de manière inhabituelle. La raison est simple. Quand vous écrivez du
côté PS dans une EMIO la durée entre deux écritures est de l’ordre de la centaine de
nanosecondes. Il n’y a pas de raison pour que les changements sur l’EMIO soient synchrones
avec le 100 MHz passé à la PL. Il faut donc détecter le changement sur l’EMIO (le passage à 1
dans notre cas) et délivrer un signal à 1 pendant une période d’horloge. Prenons l’exemple du
reset. Nous allons resynchroniser le reset sur l’horloge (reset_r1), le retarder (reset_r2)
et calculer sreset = reset_r1 and not reset_r2. Cela nous permet de détecter
la montée du reset.

clk100

reset

reset_r1

reset_r2

sreset

Nous traitons le signal valid de la même manière, ce qui nous donne le CE du compteur. Pour
mémoire, je vous rappelle le code du design dans la fenêtre suivante.

141
Vous pouvez vérifier dans la simulation le bon fonctionnement du reset (initialisation du
compteur) et de la validation (incrémentation d’une unité du compteur).

4.1.7 Recopie du code de simulation dans le Wrapper


Une fois que la simulation vous convient, il va falloir recopier le code VHDL du fichier
tp9_simul.vhd dans le fichier design_1_wrapper.vhd. Ouvrez les deux fichiers,
puis faites des copier-coller du premier vers le deuxième. Il faut copier le package :

Les déclarations de signaux :

142
Et le code :

Sauvez le fichier et vérifiez qu’il n’y a pas d’erreurs de syntaxe. Quand tout est prêt générez
le fichier .bit et chargez-le dans la maquette. Nous pouvons exporter le projet vers le SDK.
Cliquez sur le menu « File », puis sur « export », puis sur « Export Hardware… ». Dans la
fenêtre qui s’ouvre, cochez la case « Include Bitstream » puis cliquez sur « OK ». Lancez le
SDK.

143
4.1.8 SDK
Dans le SDK, reprenez toutes les étapes précédentes, y compris le lancement du projet
helloworld pour vérifier la connexion. Puis remplacez le code de helloworld par le code C
suivant :

#include <stdio.h>
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"

#define EMIO_86 86
#define EMIO_87 87

int main(void)
{
int i;
u32 data;
XGpioPs_Config *ConfigPtrPS;
XGpioPs mio_emio;
xil_printf("essai EMIO compteur TP9\n");

ConfigPtrPS = XGpioPs_LookupConfig(0);
if (ConfigPtrPS == NULL) {
return XST_FAILURE;
}
XGpioPs_CfgInitialize(&mio_emio, ConfigPtrPS, ConfigPtrPS->BaseAddr);

// EMIO bank IO n°2 : input


XGpioPs_SetDirection(&mio_emio, 2, 0x00000000); // bank2 : emio 54 -> 85 = entrée
XGpioPs_SetOutputEnable(&mio_emio, 2, 0x00000000); // pas obligatoire

data = XGpioPs_Read(&mio_emio, 2);


xil_printf("compteur non init en hexa = %x\n", (unsigned int)data);

// EMIO pin IO 86 and 87


XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_86, 1); // pas obligatoire
XGpioPs_SetOutputEnablePin(&mio_emio, EMIO_87, 1);
XGpioPs_SetDirectionPin(&mio_emio, EMIO_86, 1); // reset = sortie
XGpioPs_SetDirectionPin(&mio_emio, EMIO_87, 1); // valid = sortie

XGpioPs_WritePin(&mio_emio, EMIO_86, 0x0);


XGpioPs_WritePin(&mio_emio, EMIO_87, 0x0);

XGpioPs_WritePin(&mio_emio, EMIO_86, 0x1); // reset


XGpioPs_WritePin(&mio_emio, EMIO_86, 0x0);

data = XGpioPs_Read(&mio_emio, 2);


xil_printf("compteur apres init en hexa = %x\n", (unsigned int)data);

XGpioPs_WritePin(&mio_emio, EMIO_87, 0x1); // one count


XGpioPs_WritePin(&mio_emio, EMIO_87, 0x0);

data = XGpioPs_Read(&mio_emio, 2);


xil_printf("compteur + 1 en hexa = %x\n", (unsigned int)data);

144
for(i = 0; i < 65536; i++) {
XGpioPs_WritePin(&mio_emio, EMIO_87, 0x1); // one count
XGpioPs_WritePin(&mio_emio, EMIO_87, 0x0);
}

data = XGpioPs_Read(&mio_emio, 2);


xil_printf("compteur + 65536 en hexa = %x\n", (unsigned int)data);

return 0;
}

Vérifiez le bon fonctionnement du design.

4.2 Démarrage de la carte depuis la SD Card

Nous pouvons maintenant créer un fichier de démarrage sur la SD Card. Par défaut, La SD
Card montée sur la ZC702 contient une partition de 8 Go formatée en FAT32 :

Eteignez la ZC702, retirez la SD Card puis insérez-la dans le lecteur amovible du PC. Elle
contient différents fichiers nécessaires au test de la carte. Nous allons créer un répertoire
sauve et les déplacer dedans si ce n’est pas déjà fait. Nous allons maintenant ajouter deux
librairies dans le BSP. Cliquez sur « Xilinx », « Board Support Package Settings » :

Dans la fenêtre qui s’ouvre, selctionnez le BSP, puis cliquez sur OK :

145
Cochez les cases xilffs et xilrsa, puis cliquez sur « OK » :

146
Dans le SDK, Le projet tp9 est recompilé. Attendez la fin, puis créez un nouveau projet pour
pouvoir générer le FSBL :

Nous allons l’appeler essai_fsbl et il utilisera le BSP existant (tp9_bsp) :

Sélectionnez Zynq FSBL, puis cliquez sur « Next » :

147
Après un moment, le FSBL est créé :

Il s’agit d’un fichier binaire exécutable au format ELF (Executable and Linkable Format)
comme le fichier binaire du projet tp9. Nous pouvons maintenant créer l’image de boot.
Sélectionnez tp9, puis cliquez sur « Xilinx », « Create Boot Image » :

148
L’assistant apparait. Normalement, tout est correctement rempli :

Dans l’image de boot, vous devez voir dans l’ordre :


1. Le FSBL essai_fsbl.elf,
2. Le fichier de configuration design_1_wrapper.bit,
3. Le fichier binaire du projet tp9.elf.

149
Cliquez sur le bouton « Create Image ». Un dossier bootimage est créé dans le dossier tp9
Il contient :
• un fichier tp9.bif qui contient les informations de création de l’image de boot.

• un fichier BOOT.BIN. C’est l’image de boot. Elle sera utilisée pour le boot depuis la SD
Card.

Sélectionnez le fichier BOOT.BIN, faites un clic droit, puis cliquez sur « Copy ».

Puis collez le fichier sur la racine de la partition BOOT de la SD Card :

150
Ejectez la SD Card du PC, puis remettez-là dans le support de la carte. Pour démarrer depuis
la SD Card, il faut placer SW16 dans l’état suivant :

Fermez le SDK. Eteignez puis rallumez la carte. Vous trouverez sur le bureau l’icone de Tera

Term . Double cliquez dessus. Vous devez utiliser COM5, 115200, 8N1. Le
programme doit démarrer après chargement du .bit (regardez la led Done sur la carte : ds3) :

Si les lignes apparaissent décalées, il faut changer les réglages du terminal en cliquant sur :

151
Puis en indiquant les réglages suivants pour la fin de ligne :

Si vous voulez relancer le programme, appuyez sur le bouton SW1 :

Remettez tous les switches de SW16 à gauche.

4.3 Exercice

Dans cet exercice, vous allez reprendre le tp précédent et le modifier. Nous souhaitons réaliser
le design suivant.

PS PL

Compteur 32 bits
32
GPIO_I 1 parmi N

Reset CE Init

5
GPIO_O
7
FCLK0

152
Le compteur compte en 1 parmi N, des poids faibles vers les poids forts :

00000000000000000000000000000001

00000000000000000000000000000010

00000000000000000000000000000100

10000000000000000000000000000000

00000000000000000000000000000001

La valeur d’initialisation du compteur sera égale à 2val, val étant codé sur 5 bits.
L’initialisation sera réalisée quand reset est égal à 1. Voici quelques exemples d’initialisation :

val Initialisation compteur

0 00000000000000000000000000000001

5 00000000000000000000000000100000

31 10000000000000000000000000000000

Nous allons utiliser la bank 2 des EMIO (32 bits) en entrée plus 7 EMIO supplémentaires en
sortie. Le tableau suivant vous indique les connexions à respecter :

EMIO numéro Branché sur sens remarque

54 à 85 Sortie compteur entrée Une bank 32 bits

86 reset sortie Durée > une période d’horloge

87 CE sortie Durée > une période d’horloge

88 à 92 val sortie Init compteur = 2^val

153
Vous allez reprendre toutes les étapes vues précédemment :
• Création d’un projet tp9_1, du block design, écriture du code VHDL et simulation,
génération du bitstream et téléchargement.
• Création du projet logiciel avec le SDK, écriture du code C et test du bon fonctionnement.

154
5 Travail pratique n°10

5.1 PS avec PL, AXI GPIO connecté sur 4 switches et 8 leds

5.1.1 Introduction
Dans ce TP, nous allons utiliser le PS avec la PL et des GPIO pour pouvoir accéder aux 2
boutons poussoirs, aux deux switches et aux 8 LEDs connectés sur la PL. Les GPIO sont des
E/S d’usage général qui sont reliées à un périphérique nommé AXI_GPIO connecté au bus
AXI. Ce périphérique est créé dans la PL. On lira la valeur des boutons SW5 et SW7 et des
switches SW12-1 et SW12-2 et on allumera les leds DS15 à DS22 via le programme en C, à
travers la logique programmable. Le tableau suivant vous indique les connexions à respecter :

GPIO Branché sur sens remarque

Channel 2 8 Leds DS15 à DS22 sortie Placer les broches dans le fichier de contraintes

Channel 1 Bouton SW5 et SW7 entrée Placer les broches dans le fichier de contraintes

Channel 1 Switches SW12-1 et SW12-2 entrée Placer les broches dans le fichier de contraintes

Créez un nouveau projet TP10 puis éteignez et rallumez la maquette. Remettez tous les
switches de SW16 à gauche :

Suivez ensuite les étapes vues précédemment. Les modifications suivantes doivent être
apportées.

5.1.2 Block Design


- Ajoutez le composant Zynq, puis faites un « Run Block Automation ».

155
- Ajoutez un composant AXI_GPIO en faisant un clic droit dans le Block Design :

Puis en tapant gpio dans la fenêtre de recherche :

- Double cliquez ensuite sur le composant inséré afin de le configurer. Sélectionnez l’onglet
« IP Configuration » et configurez l’IP comme dans l’image suivante (GPIO = channel 1,
entrée, 4 bits. GPIO2 = channel 2, sortie, 8 bits) :

156
1

3
5

Cliquez sur « OK » pour fermer la fenêtre de configuration de l’AXI GPIO.

- Cliquez ensuite sur « Run Connection Automation » :

157
Dans la fenêtre qui s’ouvre, cochez la case S_AXI pour connecter l’IP avec le bus AXI. Ne
cochez pas les cases GPIO et GPIO2, nous allons traiter ces signaux manuellement.
Cliquez sur « OK ».

- Vivado a ajouté plusieurs composants supplémentaires. Pour bien voir le schéma du Block
Design, il faut mettre la fenêtre plein cadre. Pour cela, nous allons détacher la fenêtre du
gestionnaire en cliquant sur « Float » :

Puis une fois détachée, nous allons cliquer sur « Maximize » :

158
Pour rattacher la fenêtre au gestionnaire, il suffit de cliquer sur « Dock » :

Pour mettre le schéma du Block Design à l’échelle de la fenêtre, il faut cliquer sur le
bouton « Zoom fit » de la barre d’outils :

- Cliquez sur le signe + à droite des noms GPIO et GPIO2 pour faire apparaitre les signaux
internes. Vous devez obtenir ceci :

Sélectionnez les signaux internes du composant GPIO et connectez-les en externe


(sélection, clic droit, « Make External ») :

Régénérez le Layout puis validez le design. Vous devez finalement obtenir le Block Design
suivant. Fermez puis sauvez le BD.

159
5.1.3 Wrapper
Il faut maintenant générer les produits de sortie puis créer le Wrapper (géré par Vivado). Nous
désirons réaliser le design suivant :

PS PL

4 4
GPIO_IO_I

8 8
GPIO2_IO_O

Le fichier design_1_wrapper.vhd généré automatiquement correspond à notre besoin.


En effet, les GPIO du PS ont été reliées directement sur les E/S du top level ce que nous
souhaitions faire. Il n’y a donc rien à changer.

160
5.1.4 Contraintes
Il faut toutefois créer un fichier de contraintes pour relier ces E/S au bon endroit sur la carte
ZC702. Son contenu est le suivant :
#connexion aux leds 15-16-17-18-22-21-20-19
set_property PACKAGE_PIN E15 [get_ports {gpio2_io_o_0[7]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[7]}]
set_property PACKAGE_PIN D15 [get_ports {gpio2_io_o_0[6]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[6]}]
set_property PACKAGE_PIN W17 [get_ports {gpio2_io_o_0[5]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[5]}]
set_property PACKAGE_PIN W5 [get_ports {gpio2_io_o_0[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[4]}]
set_property PACKAGE_PIN V7 [get_ports {gpio2_io_o_0[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[3]}]
set_property PACKAGE_PIN W10 [get_ports {gpio2_io_o_0[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[2]}]
set_property PACKAGE_PIN P18 [get_ports {gpio2_io_o_0[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[1]}]
set_property PACKAGE_PIN P17 [get_ports {gpio2_io_o_0[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio2_io_o_0[0]}]

161
# connect to SW5 , SW7 , SW12-1 , SW12-2
set_property PACKAGE_PIN G19 [get_ports {gpio_io_i_0[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio_io_i_0[0]}]
set_property PACKAGE_PIN F19 [get_ports {gpio_io_i_0[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio_io_i_0[1]}]
set_property PACKAGE_PIN W7 [get_ports {gpio_io_i_0[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio_io_i_0[2]}]
set_property PACKAGE_PIN W6 [get_ports {gpio_io_i_0[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {gpio_io_i_0[3]}]

Quand tout est prêt, il faut synthétiser, puis implémenter le design, créer le fichier .bit
(bitstream) et le charger dans la maquette. C’est seulement ensuite que nous allons exporter le
projet vers le SDK (en oubliant pas d’inclure le bitstream).

5.1.5 SDK
Dans le SDK, reprenez toutes les étapes déjà vues, y compris le lancement du projet
helloworld pour vérifier la connexion. Puis remplacez le code de helloworld par le
code C suivant :
#include <stdio.h>
#include <sleep.h>
#include "xparameters.h"
#include "xgpio.h"

int main()
{
int Status, i;
u32 Data_out, Data_in;
XGpio Gpio; /* The Instance of the GPIO Driver */

xil_printf("essai tp10\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);

162
Data_out = 0;

for (i = 0; i < 1000; i++) {


// les 8 bits faibles de data_out sont
// envoyés sur channel 2
XGpio_DiscreteWrite(&Gpio, 2, Data_out);

Data_out++;
usleep(100000); // attente 100 ms

// lecture dans l'ordre de SW5 , SW7 , SW12-1 , SW12-2


Data_in = XGpio_DiscreteRead(&Gpio, 1);
xil_printf("Datain = %x\n", Data_in);
}

return 0;
}

A ce point du cours, vous devez être capable de comprendre ce programme tout seul. Testez le
bon fonctionnement du design.

5.2 PS avec PL, AXI GPIO connecté sur compteur 8 bits, analyseur logique

5.2.1 Introduction
Dans ce TP, nous allons utiliser le PS avec la PL, des GPIO ainsi que des compteurs en
VHDL. Le but est de vous montrer l’utilisation de l’analyseur logique utilisable dans le FPGA
(ILA : Integrated Logic Analyzer) et configurable depuis Vivado. Cet analyseur vous
permettra de réaliser la mise au point de votre design directement dans le FPGA en temps réel
conjointement avec le déroulement du programme en C. Cela vous permettra de trouver des
erreurs impossibles à voir en simulation. Nous souhaitons réaliser le design suivant :

PS PL

8
Compteur 1076 Compteur 8 bits Leds
ceo ce8 ce
Reset CE Reset
clear valid
GPIO_O
2
clk100
CLK0 (100 MHz)

163
Le PS fournit à la PL une horloge 100 MHz CLK0 ainsi qu’un signal reset et une validation.
Le premier compteur génère une validation tous les 107 périodes d’horloge, soit 10 fois par
seconde. Le compteur 8 bits, relié aux 8 leds de la carte, va donc compter en binaire et allumer
les leds au rythme du 10 Hz. Le tableau suivant vous indique les connexions à respecter :

GPIO Branché sur sens

Channel 1 clear sortie

Channel 1 valid sortie

Créez un nouveau projet TP10_1 puis éteignez et rallumez la maquette. Suivez ensuite les
étapes vues précédemment. Les modifications suivantes doivent être apportées.

5.2.2 Block Design


- Ajoutez le composant Zynq, puis faites un « Run Block Automation ».

- Double cliquez sur le composant Zynq, puis cliquez sur « Clock Configuration », « PL
Fabric Clocks » et réglez la fréquence de clk0 à 100 MHz :

Cliquez sur « OK » pour revenir au Block Design.

164
- Cliquez sur la sortie FCLK_CLK0, faites un clic droit puis cliquez sur « Make External » :

- Ajoutez un composant AXI_GPIO en faisant un clic droit dans le Block Design :

Puis en tapant gpio dans la fenêtre de recherche :

165
- Double cliquez ensuite sur le composant inséré afin de la configurer. Sélectionnez l’onglet
« IP Configuration » et configurez l’IP comme dans l’image suivante (GPIO = channel 1,
sortie, 2 bits) :

Cliquez sur « OK » pour fermer la fenêtre de configuration de l’AXI GPIO.

166
- Cliquez ensuite sur « Run Connection Automation ». Dans la fenêtre qui s’ouvre, cochez la
case S_AXI pour connecter l’IP avec le bus AXI. Ne cochez pas la case GPIO, nous
allons traiter ce signal manuellement. Cliquez sur « OK ».

- Vivado a ajouté plusieurs composants supplémentaires. Pour bien voir le schéma du Block
Design, il faut mettre la fenêtre plein cadre.

- Cliquez sur le signe + à droite du nom GPIO pour faire apparaitre les signaux internes.
Vous devez obtenir ceci :

Sélectionnez la sortie gpio_io_o et connectez-la en externe (sélection, clic droit, « Make


External »). Régénérez le Layout puis validez le design. Vous devez finalement obtenir le
Block Design suivant. Fermez puis sauvez le BD.

167
Il faut maintenant générer les produits de sortie puis créer le Wrapper (mode copie).

5.2.3 Wrapper
Dans le wrapper design_1_wrapper.vhd, créez les signaux suivants :

Connectez-les ici :

168
Puis supprimez les lignes suivantes dans l’entité (attention, pas de point-virgule sur la dernière
ligne) :

Et ajoutez la sortie leds sur 8 bits afin d’obtenir les E/S suivantes. Sauvez le wrapper et
vérifiez l’absence d’erreurs.

Retirez le wrapper et le block design des fichiers utilisés pour la simulation en décochant la
case simulation dans les propriétés de ces deux fichiers.

5.2.4 Modèle de simulation


Pour la simulation, créez un fichier tp10_1_simul qui contiendra le code VHDL suivant :

169
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.ALL;

entity tp10_1_simul is
port (
clk100 : in STD_LOGIC;
gpio_o : in STD_LOGIC_VECTOR (1 downto 0);
leds : out STD_LOGIC_VECTOR (7 downto 0)
);
end tp10_1_simul;

architecture Behavioral of tp10_1_simul is


signal cnt10M : std_logic_vector(23 downto 0);
signal cnt8 : std_logic_vector(7 downto 0);
signal ce8, clear, valid : std_logic;
begin

process(clk100) begin
if (rising_edge(clk100)) then

clear <= gpio_o(0); -- synchronisé pour le debug


valid <= gpio_o(1); -- synchronisé pour le debug
leds <= cnt8; -- synchronisé pour le debug

if (clear = '1') then


cnt8 <= (others => '0');
cnt10M <= (others => '0');
ce8 <= '0';
else
if (valid = '1') then
-- if (cnt10M = 9999999) then –- pour le hard
if (cnt10M = 9) then –- pour la simulation
cnt10M <= (others => '0');
ce8 <= '1';
else
cnt10M <= cnt10M + 1;
ce8 <= '0';
end if;
end if;

if (ce8 = '1') then


cnt8 <= cnt8 + 1;
end if;
end if;

end if;
end process;

end Behavioral;

Quelques remarques concernant ce code VHDL :

170
- Le composant AXI_GPIO est synchronisé avec l’horloge 100 MHz (il suffit de regarder le
schéma du Block Design pour le voir). Donc les signaux gpio_o sont synchrones avec le
100 MHz et ne sont pas traités comme dans tp9 (voir page 141).
- L’analyseur logique de Vivado ne peut voir que les sorties de bascule D dans le design
VHDL à condition qu’elles ne soient pas reliées à une broche de sortie du FPGA (c’est le
cas pour leds). Comme nous observerons, entre autres, les signaux clear, Valid et leds, ils
ont été resynchronisés dans la branche sensible à l’horloge clk100.
- Comme le compteur cnt10M compte jusqu’à 10 millions avant que le compteur cnt8 soit
incrémenté, la simulation peut durer très longtemps avant de voir une activité intéressante
sur les chronogrammes. Afin de corriger ce problème, nous allons tricher. Pour la
simulation, nous allons utiliser la ligne :

if (cnt10M = 9) then –- pour la simulation

Avec une horloge 100 MHz et une durée de 1000 ns (100 périodes d’horloge), nous
verrons cnt8 changer 10 fois, ce qui nous permettra de vérifier le design. Par contre, dans le
wrapper (pour le FPGA), nous utiliserons la bonne valeur :

if (cnt10M = 9999999) then –- pour le hard

5.2.5 Testbench
Créez le testbench tp10_1_simul_tb sur le site de Doulos, puis complétez le process :
stimulus: process
begin
gpio_o <= "01"; -- reset
wait for 10 ns;
gpio_o <= "00"; -- attente
wait for 100 ns;
gpio_o <= "10"; -- validation cnt10M
wait;
end process;

Lancez la simulation. Vous devez obtenir des chronogrammes qui ressemblent aux suivants.
Prenez le temps nécessaire pour comprendre précisément le fonctionnement du code VHDL
en vous aidant avec les chronogrammes.

171
5.2.6 Recopie du code de simulation dans le Wrapper
Une fois que la simulation vous convient, il va falloir recopier le code VHDL du fichier
tp10_1_simul.vhd dans le fichier design_1_wrapper.vhd. Ouvrez les deux
fichiers, puis faites des copier-coller du premier vers le deuxième. Il faut copier le package :

Les déclarations de signaux :

Et le code (attention à bien remettre la condition « if (cnt10M = 9999999) then »


dans le code VHDL) :

172
Vérifiez qu’il n’y a pas d’erreurs de syntaxe, puis sauvez le fichier.

5.2.7 Contraintes
Il faut maintenant créer un fichier de contraintes pour relier les sorties leds au bon endroit
sur la carte ZC702. Son contenu est le suivant. Quand tout est prêt, vous pouvez synthétiser
le design.
#connexion aux leds 15-16-17-18-22-21-20-19
set_property PACKAGE_PIN E15 [get_ports {leds[7]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[7]}]
set_property PACKAGE_PIN D15 [get_ports {leds[6]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[6]}]
set_property PACKAGE_PIN W17 [get_ports {leds[5]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[5]}]
set_property PACKAGE_PIN W5 [get_ports {leds[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[4]}]
set_property PACKAGE_PIN V7 [get_ports {leds[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[3]}]

173
set_property PACKAGE_PIN W10 [get_ports {leds[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[2]}]
set_property PACKAGE_PIN P18 [get_ports {leds[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[1]}]
set_property PACKAGE_PIN P17 [get_ports {leds[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {leds[0]}]

5.2.8 Insertion d’un analyseur logique (ILA)


En utilisant l’analyseur intégré (ILA : Integrated Logic Analyzer), vous pouvez voir
l’évolution des signaux internes de votre design dans le FPGA, donc en temps réel, sans
modifier votre code VHDL. Pour cela, Vivado doit insérer un composant (core) ILA après
synthèse. De nombreuses options sont apparues dans le navigateur. Cliquez sur « Set Up
Debug » :

Dans la fenêtre qui apparait, cliquez sur « Next » :

174
Le design synthétisé apparait à l’écran ainsi que la fenêtre qui va nous permettre de
sélectionner les signaux à débugger.

En cliquant sur le dossier Nets, vous voyez apparaitre les signaux internes du design :

175
ATTENTION : les signaux que vous voyez ici sont les signaux après synthèse et pas ceux
déclarés dans votre design. Le synthétiseur a optimisé le design et supprimé (ou fusionné) les
signaux qu’il a estimé inutiles. Par exemple, tous les signaux non utilisés (y compris ceux non
connectés à l’extérieur du design) seront supprimés par défaut. Normalement, les signaux
utiles placés en sorties d’une bascule D persistent après synthèse. C’est pour cela que nous
avons resynchronisé clear, valid et cnt8 sur l’horloge clk100.

Pour debugger un signal, il faut le sélectionner dans le dossier Nets, puis le tirer dans la
fenêtre Set Up Debug. Dans cet exemple, le signal cnt8 a changé en cnt8_reg(8). Reg veut dire
qu’il s’agit d’une sortie de registre et on en trouve la confirmation avec la colonne « Driver
Cell » qui indique FDRE (ce qui indique chez xilinx une bascule D avec clock enable et reset
synchrone). L’horloge qui alimente cette bascule (colonne « Clock Domain ») est bien
FCLK_CLK0.

Vous pouvez maintenant ajouter les signaux intéressants de notre design afin d’obtenir la
fenêtre suivante puis cliquer sur « Next » :

176
Dans la fenêtre suivante, nous allons laisser le nombre de valeurs d’acquisition à 1024 (on
peut monter jusqu’à 128x1024, mais cela consomme beaucoup de mémoire interne du
FPGA) :

177
Dans cette dernière fenêtre, vous devez voir que Vivado a bien ajouté un ILA (debug core) :

Les signaux en cours de debug doivent maintenant apparaitre avec une marque dans le dossier
Nets (un petit scarabée : un bug, en anglais, c’est un insecte) :

178
Fermez le design synthétisé en cliquant sur la croix :

Cliquez sur « OK » :

Puis sur « Yes » pour sauver les modifications :

Puis sur « OK ». Si le design synthétisé ne se ferme pas, recliquez sur la croix une 2ème fois.

Tout ce que nous avons fait s’est traduit par des commandes TCL ajoutées dans notre fichier
de contraintes. Vous pouvez générer le bitstream (c’est plus long, environ 8 minutes).

179
5.2.9 Lancement du programme et visualisation des chronogrammes
Dernière étape, le lancement du programme en C et la visualisation conjointe des
chronogrammes. Il reste une dernière subtilité à comprendre. L’ILA a besoin de l’horloge
clk100 pour pouvoir s’exécuter correctement et cette horloge ne démarre qu’après un premier
lancement d’un programme en C. En effet, c’est le script ps7_init.tcl du BSP qui
initialise les PLL du processeur ARM et donc qui démarre les horloges. Ce script doit donc
être lancé au moins une fois après rallumage de la carte. Autre problème, le programme final
qui pilote les AXI_GPIO ne peut pas s’exécuter (il démarre, mais plante en cours d’exécution
car il ne trouve pas le périphérique) si le bitstream n’est pas chargé. Cruel dilemme ! Mais la
solution est simple. Nous allons respecter les étapes suivantes (la procédure détaillée se trouve
page suivante) :
- Première étape, lancement de hello_world.c avec le code C par défaut. L’horloge 100 MHz
démarre.
- Deuxième étape, chargement du bitstream avec le Hardware Manager de Vivado. L’ILA
démarre correctement car il trouve une horloge 100 MHz active.
- Troisième étape, saisie dans hello_world.c du code C final qui peut maintenant s’exécuter
correctement car l’AXI_GPIO qui est réalisé dans la PL a été chargé.
- Quatrième étape, réglage des conditions de déclenchement de l’ILA. On arme ensuite
l’analyseur et le lancement du programme C déclenche l’acquisition des signaux du design.
- Facile, non ? Voyons tout cela plus en détail.

180
Exportez le projet vers le SDK. Cliquez sur le menu « File », puis sur « export », puis sur
« Export Hardware… ». Dans la fenêtre qui s’ouvre, cochez la case « Include Bitstream »
puis cliquez sur « OK ». Lancez le SDK.

Dans le SDK, reprenez toutes les étapes déjà vues, y compris le lancement du projet
helloworld pour vérifier la connexion et initialiser l’horloge 100 MHz. Quand vous
lancerez le programme, le SDK vous signalera que le bitstream n’est pas chargé. C’est
normal, cliquez sur OK pour continuer :

Dans Vivado, ouvrez le hardware manager et charger le bitstream. Au moment de la


programmation du FPGA, vous noterez le chargement d’un nouveau fichier :

Après programmation, la fenêtre ILA apparait.

181
Pour bien voir ce que vous allez faire, détachez la fenêtre puis mettez la plein cadre.
Remplacez le code de helloworld par le code C suivant :
#include <stdio.h>
#include <sleep.h>
#include "xparameters.h"
#include "xgpio.h"
int main()
{
int Status;
XGpio Gpio; /* The Instance of the GPIO Driver */

xil_printf("essai tp10\n");

/*
* Initialize the GPIO driver
*/
Status = XGpio_Initialize(&Gpio, 0);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}

/*
* GPIO channel 1 = outputs
*
*/
XGpio_SetDataDirection(&Gpio, 1, 0x00000000);

XGpio_DiscreteWrite(&Gpio, 1, 0x00000000);
XGpio_DiscreteWrite(&Gpio, 1, 0x00000001); // reset
XGpio_DiscreteWrite(&Gpio, 1, 0x00000000);
XGpio_DiscreteWrite(&Gpio, 1, 0x00000002); // valid comptage

usleep(10000000);

XGpio_DiscreteWrite(&Gpio, 1, 0x00000000); // stop comptage

return 0;
}

182
A ce stade du TP, la compréhension doit être évidente. Exécutez-le (il dure 10 secondes), et
pendant que les leds comptent en binaire, cliquez sur le bouton suivant pour déclencher l’ILA
immédiatement. Il faut que le programme soit en cours d’exécution sinon valid vaut 0 et
cnt10M ne compte pas.

Un chronogramme apparait dans la fenêtre Waveform :

Le zoom ainsi que les marqueurs fonctionnent comme dans le simulateur. Vous pouvez de la
même manière changer le radix d’un ou plusieurs std_logic_vector ou bien passer en
analogique. Par contre, l’échelle temporelle a pour unité la période de l’horloge 100 MHz.

183
La méthode de déclenchement « immédiat » n’est pas très pratique. Nous allons créer une
condition de déclenchement. Pour cela, cliquez sur le + dans la zone « Trigger Setup » :

Dans la fenêtre suivante, sélectionnez le signal clear puis cliquez sur OK :

Clear apparait maintenant dans la zone « Trigger Setup ». Pour lui donner une valeur de
déclenchement, cliquez sur sa valeur dans la colonne « Value » :

184
Dans le menu qui apparait, mettez la valeur à 1 :

Finalement, vous devez voir ceci dans la zone « Trigger Setup » :

Cliquez ensuite sur « Run Trigger » pour armer l’analyseur :

L’ILA attend maintenant que clear passe à 1 pour déclencher l’acquisition :

185
Le clear est mis à 1 puis à 0 au début du programme C. Puis Valid passe à 1 pendant 10
secondes et retombe à 0. Lancez le programme. Le trigger va se déclencher et repasser en
mode idle (inactif en anglais) après acquisition :

Les chronogrammes suivants apparaissent. On voit que le clear passe à 1 en début


d’acquisition, puis retombe à 0 et c’est ensuite valid qui passe à 1 pendant le reste de
l’acquisition. La durée totale d’acquisition est égale à 1024 périodes d’horloge (100 MHz). Ni
cnt8 ni ce8 ne changent d’état. C’est normal, il faut attendre que cnt10M passe à 9999999
pour qu’il y ait un changement sur ces signaux.

Le marqueur T rouge indique que le déclenchement a été réalisé au milieu de la fenêtre, à


l’échantillon 512. C’est le réglage par défaut qui se trouve dans la fenêtre « Settings » de l’ILA :

186
Voici un zoom sur le début de l’acquisition :

Il est possible d’introduire aussi une condition sur l’acquisition en plus de la condition sur le
déclenchement. Nous allons pour cela modifier les réglages du trigger en cliquant sur :

Pour passer à :

187
Nous pouvons maintenant ajouter une condition dans la zone « Capture Setup » en cliquant
sur le signe + :

Dans la fenêtre qui s’ouvre, sélectionnez ce8 puis cliquez sur OK :

Il reste à lui donner la valeur 1 :

Pour obtenir :

188
Nous avons maintenant une condition pour déclencher l’acquisition (clear = 1) et une
condition pour acquérir les données (ce8 = 1). Cliquez sur « Run Trigger » pour armer
l’analyseur :

L’ILA attend maintenant que clear passe à 1 pour déclencher l’acquisition puis il fera une
lecture à chaque fois que ce8 vaudra 1. L’acquisition est donc beaucoup plus lente car ce8
vaut 1 seulement 10 fois par seconde. Le programme ne durant que 10 secondes, on ne pourra
acquérir au maximum que 100 valeurs.

Lancez le programme. Vous noterez qu’après le déclenchement, ce8 est à 1 en permanence et


que seul cnt8 s’incrémente.

189
5.3 Exercice

Dans cet exercice, nous allons modifier le design précédent afin de rajouter les fonctionnalités
suivantes :
• Chargement dans le compteur 8 bits d’une valeur de démarrage (Start) et d’une valeur
d’arrêt (Stop). Start et Stop auront les propriétés suivantes : 0 < Start < 255, 0 < Stop <
255, Start < Stop. Les valeurs seront chargées dans le compteur 8 bits au moment de
l’initialisation synchrone.
• Ajout d’une commande compteur/décompteur (Up/Down).
• Interrogation du compteur 8 bits pour connaitre sa valeur courante.

Vous mettrez au point le design en utilisant un ILA qui vous servira aussi à mesurer le temps
de lecture de la valeur du compteur en cycles d’horloge. Le schéma du montage est le suivant :

PS PL
GPIO_I
9 8
Compteur 107 Data data_ready
8
ceo ce8 ce Compteur 8 bits
Reset CE Reset Start Stop U_D ask4data Leds

clear valid 8 8
GPIO_O
20
2
clk100
CLK0 (100 MHz)

Précisons maintenant la méthode utilisée pour transférer la valeur du compteur de manière


fiable (on pourrait faire plus simple dans cet exercice, mais c’est l’occasion d’étudier un
protocole de transfert de données). Pour transférer des données avec un microprocesseur, vous
avez au moins deux méthodes traditionnelles :
1) Les interruptions. Dans ce cas, le périphérique signale au PS qu’une donnée est prête en
activant une interruption matérielle. Le PS arrête le programme en cours et saute dans une
routine de traitement spéciale pour récupérer la donnée, la stocker puis signaler à la PL
qu’il l’a bien récupérée. Puis il revient automatiquement dans le programme principal.
Nous verrons un exemple d’interruption avec un timer dans tp12.

190
2) Le polling (sondage en anglais). C’est le programme principal qui doit scruter un signal qui
lui indique qu’une donnée est prête pour la récupération. Il y a obligatoirement dans le
programme principal une boucle pour aller vérifier l’état de ce signal de manière régulière.
C’est la méthode que nous allons utiliser dans cet exercice.

Dans les deux méthodes, il faut prévoir de la signalisation pour réaliser le transfert de manière
fiable. De nombreux protocoles de transfert existent, notamment dans le domaine des réseaux.
Nous allons voir une méthode simple basée sur les signaux RTS/CTS. Dans ce cas, c’est
l’émetteur qui initie la transaction :

phase Emetteur Récepteur


1 RTS Request To Send (RTS = 1) Attente RTS = 1
Es-tu prêt à recevoir une donnée ?
2 CTS Attente CTS = 1 Clear To Send (CTS = 1)
Je suis prêt à recevoir une donnée
3 Envoi donnée J’envoie la donnée Attente RTS = 0
je mets RTS = 0
4 Lecture donnée Attente CTS = 0 Lecture de la donnée
acquittement CTS = 0

Nous allons utiliser une version simplifiée de ce protocole. Le tableau suivant vous indique les
connexions à respecter :

GPIO Branché sur sens remarque

Channel 1 (7:0) Start (8 bits) sortie Synchrone sur clk0

Channel 1 (15:8) Stop (8 bits) sortie Synchrone sur clk0

Channel 1 (19:16) ask4data, U_D, valid, clear sortie Synchrone sur clk0

Channel 2 (7:0) Data (8 bits) entrée Synchrone sur clk0

Channel 2 (8) Data_ready entrée Synchrone sur clk0

La lecture se déroulera de la manière suivante. C’est un modèle client/serveur. La PL (serveur)


attend une demande de lecture qui est initiée par le PS (client).

191
PS (client) PL (serveur)

ask4data passe de 0 à 1 Attente ask4data = 1

Attente Data_ready = 1 Data_ready passe de 0 à 1

écriture cnt8 dans data

Lecture data
Attente ask4data = 0
ask4data passe de 1 à 0

Attente Data_ready = 0 Data_ready passe de 1 à 0

Pour vérifier que la transaction est correcte dans le programme en C, on pourrait la répéter
plusieurs fois en arrêtant le compteur (valid = 0) et vérifier que la valeur obtenue est toujours
la même. Cela permettrait aussi d’estimer précisément le temps de transfert. On pourrait aussi
initialiser le compteur avec plusieurs valeurs de Start puis lire son état et vérifier que la valeur
obtenue est à celle de Start (avec Valid = 0 pour arrêter le compteur).

Il reste à expliquer l’écriture en VHDL d’une machine d’état (FSM : Finite State Machine) qui
met en œuvre un séquencement. Le diagramme suivant implémente le protocole RTS/CTS
côté récepteur. Il contient 2 états :

FSM récepteur Récepteur

RTS_ps2pl = 1 Attente RTS = 1

Clear To Send (CTS = 1)


condition STATE0 CTS_pl2ps = 1
Je suis prêt à recevoir une donnée
Attente RTS = 0
RTS_ps2pl = 0

CTS_pl2ps = 0 Lecture de la donnée


STATE1 Lecture data
CTS = 0

192
Voyons maintenant le code VHDL correspondant :

1. architecture a1 of machine_etat is
2. TYPE state_type IS (state0, state1);
3. SIGNAL sreg : state_type;
4. BEGIN

5. PROCESS(clk100)BEGIN
6. IF (clk100’event and clk100='1') THEN
7. if (clear = '1') then condition
8. sreg <= state1;
9. CTS_pl2ps <= '0';
10. else

11. CASE sreg IS


12. WHEN state0 =>
13. if (RTS_ps2pl = '0') then
14. sreg <= state1;
15. data <= Data_ps2pl; Préparation de l’état suivant
16. CTS_pl2ps <= '0';
17. end if;
18. WHEN state1 =>
19. if (RTS_ps2pl = '1') then
20. CTS_pl2ps <= '1';
21. sreg<= state0; Préparation de l’état suivant
22. end if;
23. WHEN OTHERS =>
24. sreg <= state1;
25. CTS_pl2ps <= '0';
26. END CASE;

27. end if;


28. END IF;
29. END PROCESS;
30. end;

On crée à la ligne 2 un type énuméré appelé state_type et on crée un signal sreg de ce


type. sreg ne peut prendre que les états STATE0 à STATE1 qui seront traduit par le
synthétiseur soit par un compteur binaire, soit par un compteur 1 parmi N ou encore par un
compteur Gray. Cela fait partie des paramètres du synthétiseur concernant les machines d’état.
Dans la branche sensible à l’horloge clk100, à la ligne 8, on initialise sreg à STATE1 puis
on crée un case.

Lignes 12 à 17, si sreg vaut STATE0 alors si RTS_ps2pl=0, sreg = STATE1, on met
CTS_pl2ps à 0 et on lit la donnée. En sortant du process, sreg passe à STATE1.

Lignes 18 à 22, si sreg vaut STATE1 alors si RTS_ps2pl=1, sreg = STATE0 et on met
CTS_pl2ps à 1. En sortant du process, sreg passe à STATE0.

193
Selon le type de codage utilisé pour la machine d’état, il se peut que tous les états possibles ne
soient pas utilisés. Il ne faudrait pas être dans un état indéterminé si par malchance on se
retrouvait avec une de ces valeurs inutilisées dans le registre d’état. Pour éviter tout problème,
on ajoute une branche OTHERS dans le case (ligne 23 à 25) qui fait passer sreg à STATE1.
C’est une sécurité en cas d’état non prévu dans le diagramme.

Il faut noter qu’une autre machine d’état (celle du côté PS) devra être écrite en langage C et
fonctionner en synchronisme avec la machine d’état côté PL.

Créez un nouveau projet TP10_2 puis éteignez et rallumez la maquette. Suivez ensuite les
étapes vues dans le tp précédent. Vous pourrez éventuellement vous inspirer du code de la
machine d’état précédente pour réaliser la lecture de la valeur du compteur.

194

Vous aimerez peut-être aussi