21
1 Adrien DUBEDAT Mathilde FLASZYNSKI Paul LAHALLE Thulaxan NAGULESWARAN Rapport de Projet Encadrants : Mr. LE GAL et Mr. LEROUX 2013/2014 Projet avancé en systèmes embarqués : Extension du jeu d’instruction du processeur softcore Plasma

Projet avancé en systèmes embarqués - Welcome to … · Le microprocesseur étant sur 32bits, les données en entrée et en sortie devront obligatoirement être sur 32 its,

Embed Size (px)

Citation preview

1 Projet avancé en systèmes embarqués – 2013/2014

Adrien DUBEDAT

Mathilde FLASZYNSKI

Paul LAHALLE

Thulaxan NAGULESWARAN

Rapport de Projet

Encadrants : Mr. LE GAL et Mr. LEROUX 2013/2014

Projet avancé en systèmes

embarqués :

Extension du jeu d’instruction du

processeur softcore Plasma

2 Projet avancé en systèmes embarqués – 2013/2014

Table des matières

1 Introduction ............................................................................................. 3

2 Présentation ............................................................................................ 4

2.1 Microprocesseur Plasma ......................................................................................................... 4

2.2 Flot de conception ................................................................................................................... 4

2.2.1 Architecture du système .................................................................................................. 4

2.2.2 Méthode d’optimisation .................................................................................................. 5

2.2.3 Programmation du microprocesseur ............................................................................... 6

3 Maitrise du flot de conception ................................................................. 7

3.1 Cryptage AES ........................................................................................................................... 7

3.2 Code Golay .............................................................................................................................. 8

4 Le projet MP3 ........................................................................................... 9

4.1 Optimisations matérielles ....................................................................................................... 9

4.2 Échanges de données entre le PC et la carte ........................................................................ 12

4.2.1 Le principe...................................................................................................................... 12

4.2.2 Les outils ........................................................................................................................ 12

4.2.3 Redéfinition des fonctions ............................................................................................. 13

4.2.4 Tests et résultats............................................................................................................ 13

5 Conclusion............................................................................................... 16

6 Annexes .................................................................................................. 17

6.1 Exemple de test ALU.............................................................................................................. 17

6.1.1 AES ................................................................................................................................. 17

6.1.2 Golay .............................................................................................................................. 17

6.1.3 MULSHIFT32 .................................................................................................................. 18

6.1.4 CLZ ................................................................................................................................. 18

6.2 Exemple de test coprocesseur .............................................................................................. 19

6.2.1 MADD64 ........................................................................................................................ 19

6.3 Redéfinition des fonctions C ................................................................................................. 20

6.3.1 fopen .............................................................................................................................. 20

6.3.2 fclose .............................................................................................................................. 20

6.3.3 fread .............................................................................................................................. 20

6.3.4 fwirte ............................................................................................................................. 21

6.3.5 memset .......................................................................................................................... 21

6.3.6 strcmp ............................................................................................................................ 21

6.3.7 memmove ...................................................................................................................... 21

6.3.8 memcpy ......................................................................................................................... 21

3 Projet avancé en systèmes embarqués – 2013/2014

1 Introduction

Ce projet a pour but d’estimer les performances d’un jeu d’applications à travers un processus de

simulations et de conceptions logicielles et matérielles. L’une de ces applications est un décodeur MP3 qui nous

permettra de mettre en œuvre les différentes notions abordées durant ce projet.

Pour cela, nous utiliserons deux cartes FPGA Xilinx : une carte Virtex 5 et une carte Spartan 6 sur

lesquelles nous implémenterons l’application choisie après l’avoir simulée. Afin d’estimer les performances avant

et après la conception matérielle, nous avons suivi la démarche suivante : simulation sur PC de l’application en

l’état, modification de l’application en utilisant les ressources matérielles des cartes FPGA, nouvelle simulation sur

PC puis implémentation des deux versions sur carte afin de comparer les différents résultats.

La comparaison nous permettra de nous rendre compte de l’importance d’une conception conjointe,

quand elle est possible, puisqu’elle permet d’obtenir des gains non-négligeables en termes de nombre de cycles

d’horloge nécessaires et de baisse de consommation due à une fréquence de processeur plus faible.

Cependant, pour être en mesure d’estimer puis d’améliorer les performances d’une application, il est

nécessaire de se familiariser avec le flot de conception relativement complexe qui sera décrit par la suite.

4 Projet avancé en systèmes embarqués – 2013/2014

2 Présentation

Nous décrirons dans cette partie l’architecture matérielle et le flot de conception conjointe matériel/logiciel

qui nous a été mis à disposition pour la réalisation du projet.

2.1 Microprocesseur Plasma

Notre projet est essentiellement basé autour du microprocesseur Plasma, qui est un microprocesseur

softcore Open Source. Cela signifie qu’il nous est fourni sous forme d’une description VHDL.

Le Plasma est un microprocesseur de type RISC (Reduced instruction set computer) qui exécute des

instructions de type de MIPS-I (Microproscessor Without Interlocked Pipeline).

On notera que dans notre cas, on travaillera sur un microprocesseur un peu allégé. En effet, on ne dispose

que d’un squelette de ce microprocesseur composé uniquement des éléments de bases indispensables et de 19

ALUs (Arithmetic Logic Unit) supplémentaires.

Dans la suite du projet, nous implémenterons ce microprocesseur softcore sur deux cartes de

développement de Xilinx : Spartan 6 et Virtex 5. Sachant que le Plasma nécessite environ 1600 éléments logiques,

nous pouvons l’implémenter sur ces cartes sans problème.

2.2 Flot de conception

Nous décrirons dans cette partie le flot de conception qui est mis à notre disposition. On verra dans un

premier temps l’architecture matérielle du System on (Programmable) Chip (SoPC) qui nous est fourni et on

étudiera dans un second temps les évolutions qui pourrons lui être apportées et la programmation de ce SoPC.

2.2.1 Architecture du système

La figure ci-dessous représente l’architecture du SoPC fournie au début du projet :

Copro1 Copro2 Copro3 Copro4

Microprocesseur Plasma FIFO IN FIFO OUT

RAM(128 ko)

BUS

UART

Figure 1: Architecture du SoPC

Le système est principalement orienté autour du microprocesseur Plasma qui est relié à ses périphériques à

l’aide d’un bus de communication.

Les périphériques dont il dispose sont essentiellement :

- une mémoire RAM de 128 ko, directement implémenté sur la puce (mémoire OnChip),

- une liaison UART,

- une connexion Ethernet, au travers des deux FIFO IN/OUT,

- quatre coprocesseurs.

5 Projet avancé en systèmes embarqués – 2013/2014

Les deux FIFOs permettent d’établir une connexion Ethernet de type UDP entre le PC et la carte, de la

manière suivante :

Microprocesseur

FIFO IN

FIFO OUT

Ethernet

Figure 2: Connexion Ethernet

La FIFO IN réceptionne les paquets IP émis par le PC et stocke ces données dans une FIFO qui est accessible

au microprocesseur. La FIFO OUT effectue la tâche inverse, elle regroupe les données écrites par le

microprocesseur, les transforme en trame UDP puis les transmet vers le PC.

On notera que la pile IP de ces FIFO est codée en dur dans la description VHDL, nous devons donc configurer

le PC sur le réseau : 192.168.200 afin d’établir la connexion avec la carte.

L’architecture possède quatre coprocesseurs qui pourront être utilisés pour traiter certains algorithmes de

manière « Hardware ». En effet, ces périphériques pourront être utilisés comme accélérateurs matériels afin de

décharger le CPU.

2.2.2 Méthode d’optimisation

On dispose de deux méthodes d’optimisation pour notre système, la première consiste à utiliser les 19 ALUs

supplémentaires disponibles dans le microprocesseur Plasma, la seconde est l’utilisation des quatre

coprocesseurs.

L’utilisation des ALUs est très efficace, car elles sont directement reliées au microprocesseur. Alors que les

coprocesseurs nécessitent l’utilisation du bus de communication.

Les ALU sont tous conçus de la manière suivante :

ALU32 bits

32 bits

32 bits

Input1

Input2

Output

Figure 3: ALU

Le microprocesseur étant sur 32bits, les données en entrée et en sortie devront obligatoirement être sur 32

bits, De plus l’architecture RISC du microprocesseur Plasma oblige les ALUs à effectuer leur opération en un seul

cycle d’horloge.

Nous voyons donc les deux principaux inconvénients des ALU : traiter seulement deux données codées sur

32 bits, et n’effectuer que des opérations combinatoires car chaque instruction s’exécute en un cycle d’horloge

maximum.

6 Projet avancé en systèmes embarqués – 2013/2014

Pour pallier à cette limite imposée par les ALUs, nous pouvons utiliser les coprocesseurs, qui sont structurés

de la manière suivante :

Coprocessor32 bits32 bits

Clock

InputOutput

Reset

Input_valid

Figure 4: Coprocesseur

Contrairement aux ALUs, on voit que les coprocesseurs possèdent une entrée d’horloge, ce qui leur permet

d’effectuer des opérations plus complexes que les ALUs. De plus, ils pourront traiter un grand nombre de données

qui leur sera transmis par mot de 32 bits.

Le seul inconvénient des coprocesseurs est que la communication avec le microprocesseur s’effectue

uniquement par le biais du bus de communication. Cela signifie que l’utilisation des coprocesseurs devient

efficace uniquement lorsque le temps d’accès au bus devient négligeable face au temps de calcul.

2.2.3 Programmation du microprocesseur

Comme nous l’avons vu, le Plasma est un microprocesseur qui fonctionne avec des jeux d’instruction MIPS-I.

Cela signifie qu’il suffit d’utiliser un compilateur MIPS pour exécuter un programme sur le Plasma. On notera que

les 19 ALUs ont été rajoutées en tant qu’instruction assembleur et les coprocesseurs étant connectées sur le bus

communication, ils sont donc adressables par le microprocesseur au travers de son bus de données.

La figure ci-dessous représente l’adressage mémoire du système :

Bootloader

Programme

UART

FIFO

Copro

0x00000000

0x00010000

0x20000000

0x30000000

0x40000000

Figure 5: Adressage mémoire

Le vecteur de reset du microprocesseur est configuré à l’adresse 0x0 afin qu’il puisse démarrer sur le

bootloader. Ceci permet télécharger le programme à exécuter à l’adresse 0x10000 à l’aide de la connexion

Ethernet. On notera que le programme est directement mis à l’adresse 0x0 lors de la simulation.

Au cours de projet nous utiliserons tous ces outils qui nous fournit afin l’optimiser les instructions du

microprocesseur Plasma.

7 Projet avancé en systèmes embarqués – 2013/2014

3 Maitrise du flot de conception

Afin de comprendre et de s’exercer à la maîtrise du flot de conception, nous avons, dans un premier temps,

réaliser l’optimisation de certaines fonctions présentes dans des applications de benchmark. Parmi les nombreux

tests possibles, nous avons choisi de s’exercer sur l’AES (algorithme de cryptographie) et le Golay (algorithme pour

la correction d’erreur).

3.1 Cryptage AES

La première application que nous avons décidé d’implémenter est celle du cryptage/décryptage AES. Il s’agit

d’un standard de cryptage qui est mondialement utilisé, notamment aux USA pour le chiffrage des documents

jusqu’au niveau de sécurité « SECRET ».

Après avoir parcouru le code en langage C de cette méthode de cryptage, nous nous sommes aperçu

qu’une fonction utilisant des décalages de bits et des opérations logiques était très utilisée par l’algorithme. Nous

nous sommes donc intéressés à la simulation puis à l’implémentation matérielle de cette fonction sur la carte

FPGA Virtex 5.

Le code présenté ci-dessous est celui à optimiser :

#define Xtime(x) ((((x) & 0x7f7f7f7f) << 1) ^ ((((x) & 0x80808080) >> 7) *

0x0000001b)) //: implémentation logicielle de la fonction Xtime

L’implémentation matérielle en langage VHDL peut être schématisée à l’aide d’opérateurs « ET logique »,

de décalage de bits, d’un multiplieur (avec un opérande constant) et d’un opérateur « XOR » :

&32 bits32 bits

32 bits32 bits

0x7F7F7F7F

<<132 bits32 bits

&32 bits32 bits

INPUT

32 bits32 bits0x80808080

>>832 bits32 bits

X26 bits26 bits

278 bits8 bits

=1

32 bits32 bits

32 bits32 bits

32 bits32 bits

Figure 6 : Description matériel de l'algorithme Xtime pour l'AES

L’utilisation de cette implémentation s’effectue en redéfinissant la fonction Xtime comme fonction

traitée par l’ALU 1 :

#define Xtime(x) isa_custom_1 (x,0)

Nous avons validé cette implémentation en vérifiant que les résultats obtenus en cryptant les données de

test fournies dans le testbench étaient identiques à ceux obtenus avec le programme originel.

8 Projet avancé en systèmes embarqués – 2013/2014

Enfin, nous avons relevé les performances suivantes :

Nombre de cycles

Sans accélération matérielle 5 204

Avec accélération matérielle 4 361

3.2 Code Golay

En ce qui concerne le Golay, après avoir parcouru l’algorithme, nous avons choisi d’optimiser la méthode

INST1(pattern) puisqu’elle est très souvent appelée.

#define X11 0x00000800 /* vector representation of X^{11} */

#define GENPOL 0x00000c75 /* generator polinomial, g(x) */

u32 INST1(u32 pattern, u32 aux)

{

return (pattern ^ (aux/X11) * GENPOL);

}

Pour réaliser cette opération en un cycle d’horloge on l’implémente sur FPGA :

32 bits32 bits >>11X

32 bits32 bits 32 bits32 bits

32 bits32 bits =132 bits32 bits

32 bits32 bits

INPUT2(aux)

INPUT2(pattern)

Genepol

32 bits32 bits

rTemp1 := UNSIGNED(aux(20 downto 0) & "00000000000");

rTemp2 := UNSIGNED(aux(21 downto 0) & "0000000000");

rTemp3 := UNSIGNED(aux(25 downto 0) & "000000");

rTemp4 := UNSIGNED(aux(26 downto 0) & "00000");

rTemp5 := UNSIGNED(aux(27 downto 0) & "0000");

rTemp6 := UNSIGNED(aux(29 downto 0) & "00");

rTemp7 := rTemp1 + rTemp2 + rTemp3 + rTemp4

+rTemp5 + rTemp6 + UNSIGNED(aux);

Figure 7 : Description matériel de l'algorithme INST1 du Golay

Pour réaliser une telle optimisation nous avons besoin d’un opérateur de décalage de bits vers la droite, d’un

multiplieur (avec un opérande constant) et d’un ou exclusif. Le multiplieur prenant un opérande constant nous

avons choisi de ne pas utiliser un des multiplieurs analogiques présent dans le FPGA, ce calcul est donc réaliser par

une série de décalage et d’addition.

Sans accélération matérielle l’exécution de l’ensemble du code Golay nécessite 772 140 cycles, avec

l’optimisation réalisée plus haut nous obtenons 575 532 cycles. Cette optimisation nous permet donc un gain de

200 000 cycles d’horloges, ce qui rend notre système 25% plus performant en termes de vitesse de calcul.

9 Projet avancé en systèmes embarqués – 2013/2014

4 Le projet MP3

L’objectif de ce projet était de concevoir un circuit dédié pour le décodage de fichier MP3. Ce projet étant

déjà très avancé, notre rôle a été d’optimiser le système afin d’obtenir de meilleurs performances en terme de

vitesse de calcul. Pour améliorer ces performances, nous avons eu à réaliser ces calculs sur un circuit FPGA.

L’architecture du circuit FPGA contient un processeur softcore Plasma avec 19 ALUs et 4 Coprocesseurs

utilisable pour l’optimisation. Il a donc fallut se poser des questions quant aux ressources à utiliser.

4.1 Optimisations matérielles

Dans un premier temps il a fallu déterminer les fonctions les plus utilisées par le système de manière à

optimiser efficacement l’application. Pour cela, nous avons utilisé quelques outils et logiciels gprof pour effectuer

un profilage du code. Ce dernier permet donc d’analyser le contenu du programme et de mettre en évidence les

ressources et méthodes les plus utilisées lors de son exécution.

Ainsi, nous avons décidé d’optimiser les méthodes MULSHIFT32, CLZ et MADD64. Nous disposions de deux

machines, une première avec un processeur 64 bits et une seconde avec un processeur 32 bits. Selon la machine,

l’optimisation de la méthode MADD64 était donc inutile. Mais nous ne considérons que les résultats obtenues

pour le processeur 32 bits puisque le microprocesseur plasma est sur 32bits.

MULSHIFT32

Le MULSHIFT32 permet de récupérer les 32 bits de poids fort du résultat du produit de deux opérandes de

32 bits.

static volatile int MULSHIFT32(int x, int y)

{

__int64 r = (__int64)x * (__int64)y;//effectue le produit des deux entrées de 32 bits

//et on obtient une valeur sur 64bits

r = r >> 32; // On récupère les 32 bits de poids fort de ce nombre codé sur 64 bits.

return (int) r;

}

L’implémentation matérielle se décrit de la manière suivante :

Figure 8 : Description matériel du MULSHIFT32

Cette optimisation matérielle nécessite l’utilisation d’un multiplieur et d’un opérateur de décalage de 32 bits.

Afin de valider l’optimisation que nous avons développée, nous avons testé la fonction avec quatre valeurs

de test différentes. De manière à réaliser des tests les plus pertinents possibles, nous avons fait en sorte de

mesurer précisément le temps d’exécution de la fonction elle-même.

Voici les résultats obtenus :

Opérations Simulation sur PC Implémentation sur carte

Sans Accélération Matérielle

MULSHIFT32(0,0) 5 5

MULSHIFT32(0x10000000, 0x00000010) 8 8 MULSHIFT32(0x81111111, 0x81111111) 29 29 MULSHIFT32(0x0FFFFFFF, 0x80000100) 29 29

Avec Accélération Matérielle

MULSHIFT32(0,0) 4 4

MULSHIFT32(0x10000000, 0x00000010) 4 4 MULSHIFT32(0x81111111, 0x81111111) 4 4 MULSHIFT32(0x0FFFFFFF, 0x80000100) 4 4

10 Projet avancé en systèmes embarqués – 2013/2014

Ces valeurs de test ont été choisies afin d’effectuer deux multiplications simples puis deux multiplications

dont les valeurs d’entrées sont élevées ou négatives.

Les résultats montrent qu’une optimisation matérielle est très intéressante au niveau des performances

puisque nous obtenons un gain très important (jusqu’à 7 fois plus rapide) lorsque les données en entrées sont

complexes.

CLZ

Le but du CLZ est de compter le nombre de bit à ‘0’ entre le bit de poids fort et le dernier bit à ‘1’ d’un mot

de 32 bits.

static __inline int CLZ(int x)

{

int numZeros;

if (!x)

return (sizeof(int) * 8);

numZeros = 0;

while (!(x & 0x80000000)) { //tant que le bit de poids fort n'est pas

numZeros++; //un '1' on incrémente le compteur numZeros

x <<= 1; //et on décale x de 1 bit vers la gauche

}

return numZeros;

}

Afin de réaliser la même opération sur un cycle d’horloge, nous avons choisi d’utiliser une des ALUs du

processeur. On implémente ainsi le circuit suivant sur la cible FPGA :

MUX

32 bits32 bits

32 bits32 bits

32 bits32 bits

.

.

.

32 bits32 bits

INPUTINPUT

OUTPUTOUTPUT

00

3232

Figure 9 : Description matériel de l'algorithme du CLZ

Le CLZ nécessite l’utilisation d’un seul multiplexeur 33 entrées / 1 sortie contrôlé par l’entrée.

On s’aperçoit que 132 cycles d’horloge sont nécessaires en software et seulement 5 sont nécessaires en

hardware. Cette méthode est certes fastidieuse à développer mais elle est 26 fois plus rapide.

MADD64

La méthode MADD64 permet de réaliser la somme d’un nombre sur 64 bits avec le produit de deux nombres

sur 32 bits.

__int64 MADD64(__int64 sum, int x, int y)

{

stop();

__int64 z = (sum + ((__int64)x * y));

stop();

return z;

}

11 Projet avancé en systèmes embarqués – 2013/2014

Pour implémenter cette méthode sur FPGA, nous avons choisi dans un premier temps d’utiliser un des

coprocesseurs. Nous avons implémenté une machine d’état reposant sur trois process : affectation de l’état

présent, modification de l’état présent et affectation des sorties. Elle se compose de sept états :

Init Set_x Set_y Set_sum1 Set_sem2

Ret1 Ret2

Data_valid Data_valid Data_valid Data_valid

Data_valid

Data_valid

Data_valid

reset

Figure 10 : Machine d'état pour le calcul de MADD64

Les états :

Init : Lancement de la machine d’état.

Set_x : On récupère la 1ère

entrée sur 32 bits.

Set_y : On récupère la 2ème

entrée sur 32 bits.

Set_sum1 : On récupère les 32 bits de poids faible de l’entrée sur 64 bits.

Set_sum1 : On récupère les 32 bits de poids fort de l’entrée sur 64 bits.

Ret1 : On retourne les 32 bits de poids fort du résultat.

Ret2 : On retourne les 32 bits de poids faible du résultat.

Cette machine d’état permet de récupérer les données et de faire le calcul suivant :

X32 bits32 bits

32 bits32 bits

64 bits64 bits

X

+

64 bits64 bits

64 bits64 bits

Y

sum

Figure 11 - Description matérielle du calcul du MADD64

Avec cette implémentation, on obtient les résultats suivants : 5 cycles en software et 23 cycles en hardware.

On peut aisément voir que cette implémentation nuit aux performances de notre système. Nous aurions pu

utiliser plusieurs ALUs pour réaliser cette même opération ce qui nous aurait offert de meilleurs résultats.

Cependant, nous avons préféré nous pencher sur le décodage en lui-même plutôt que sur l’optimisation lors des

dernières séances.

12 Projet avancé en systèmes embarqués – 2013/2014

4.2 Échanges de données entre le PC et la carte

Dans cette partie nous allons réaliser l’implémentation du décodeur MP3 sur la puce FPGA. Pour cela nous

devons d’abord mettre au point un protocole d’échange de donnée entre le PC et la carte.

4.2.1 Le principe

Pour recevoir et envoyer des informations au microprocesseur, nous utilisons deux FIFOs qui permettent

d’établir une liaison Ethernet entre le PC et la puce FPGA.

La fifo_in permet de réceptionner les données émis par le PC et la fifo_out effectue l’opération inverse. Ces

FIFOs sont deux périphériques du point de vue du microprocesseur, qui sont adressable au travers du bus de

communication.

La figure ci-dessous schématise le lien Ethernet établi entre la carte et le PC :

PC Ethernet Link

FIFO IN

FIFO OUT

µP

SoPC

Figure 12: lien Ethernet

Le protocole UDP est respecté lors de la transmission des données par Ethernet. On notera que pour la

simulation, la communication sera simulée par les deux fichiers pcie_in.txt et pcie_out.txt.

4.2.2 Les outils

Nous disposons des fonctions qui nous permettent de lire, d'écrire et de savoir si l’on peut écrire dans les

FIFOs via les bus de communication. Ces fonctions sont o_write(), i_read() et o_valid() et sont définies dans le

fichier SoPC_Design.h. La fonction o_write(value) écrit à l'adresse FIFO_OUT_DATA_WRITE la donnée value dans

la FIFO_OUT. La fonction i_read() lit la donnée à l'adresse FIFO_IN_DATA_READ. Enfin, la fonction o_valid() lit à

l'adresse FIFO_OUT_VALID, et indique si la FIFO de sortie est prête, de même la fonction i_valide() indique si FIFO

d’entrée à des données en attendent de traitement.

À partir de ces fonctions de base nous devons redéfinir les fonctions suivantes : fopen, fread, fwrite, fclose.

C’est-à-dire que nous devons établir un protocole de communication entre la carte et PC afin qu’on puisse utiliser

ces fonctions pour accéder directement à partir du FPGA aux fichiers présent sur le PC.

Afin de simplifier cela, nous redéfinirons ces fonctions de telle manière à utiliser uniquement deux fichiers.

Un fichier d’entrée en lecture seule et un fichier de sortie en écriture seule.

13 Projet avancé en systèmes embarqués – 2013/2014

4.2.3 Redéfinition des fonctions

Comme on l’a vu précédemment, nous avons dû redéfinir les fonctions permettant au FPGA d’utiliser les

fichiers. Pour cela, on a défini le protocole d’échange de communication suivant :

Requête

Nombre d’octets

Lecture de fichier Écriture dans le fichier

Requête de lecture Requête d’écriture

Figure 13: protocole de communication

Le FPGA envoie deux trames Ethernet, la première contient le type de la requête (demande de lecture ou

d’écriture) et la seconde la taille du message à transmettre. Ensuite, le PC lira le fichier pour le transmettre ou

écrira les données transmise par la carte dans un fichier.

De plus, afin de simplifier l’échange entre le PC et la carte, seulement deux fichiers seront manipulés, l’un

pour la lecture et l’autre pour l’écrire. Ce qui signifie que les fonctions fread et fwrite devront être redéfinies

proprement. Quant aux fonctions fopen et fclose il suffit de les déclarer.

En ce qui concerne les fonctions de manipulation mémoire : memset, strcmp, memmove et memcpy ont été

définies comme leur homonyme C classique.

4.2.4 Tests et résultats

Nous allons maintenant voir les trois tests que nous avons effectués. Tout d’abord, nous avons commencé

par tester la liaison Ethernet. Ensuite, on a testé l’émission et la réception d’un fichier MP3. Finalement, on a testé

le décodage du fichier MP3.

4.2.4.1 Test de la connexion Ethernet

Pour tester la connexion Ethernet, on envoie une série de données stockées dans un buffer et on vérifie que

les données sont bien récupérées. Tout d’abord, il faut compiler le software embarqué sur la carte et le software

du PC, qui respectent tous les deux le protocole d’échange défini précédemment.

Le PC envoie une information sous forme de trame UDP dans la FIFO d’entrée grâce à la méthode :

sock>sync_write( LDPC_FIFO_IN_DATA, data). Ensuite, le microprocesseur lit ces données, les enregistre dans un

buffer et les renvoies vers la FIFO de sortie, grâce aux fonctions fread et fwrite redéfinies précédemment. Enfin, le

PC lit ce que le microprocesseur lui a envoyé grâce à la méthode: tmb = sock->sync_read( LDPC_FIFO_OU_DATA ).

14 Projet avancé en systèmes embarqués – 2013/2014

Voici une partie du programme exécuté sur le PC, qui correspond à l'envoie de la constante 0x00000001 via

la liaison Ethernet et à la réception d’une donnée provenant du microprocesseur :

// ON ENVOIE UNE DONNEE DANS LA FIFO IN sock->sync_write( LDPC_FIFO_IN_DATA, 0x00000001); // ON LIT UNE DONNEE DANS LA FIFO OUT while( sock->sync_read( LDPC_FIFO_OU_COUNT ) == 0 );

tmb = sock->sync_read( LDPC_FIFO_OU_DATA );

printf("DONNEE LUE = 0%8.8X\n", tmb);

4.2.4.2 Test MP3 : envoi de données à partir de fichiers sans décodage

On a précédemment validé le fonctionnement de la communication Ethernet. Maintenant, nous allons tester

l’émission et la réception d’un fichier MP3.

Le contenu du fichier est envoyé octet par octet du PC vers la carte. Ces données sont envoyées dans la FIFO

d’entrée. Ensuite, le microprocesseur recopie le contenu de la FIFO d’entrée dans la FIFO de sortie. Finalement, le

PC lit cette dernière et récupère ainsi l’ensemble du fichier précédemment envoyé octet par octet.

Il s’avère que ce test a pu être validé. Nous avons comparé le fichier MP3 d’origine et celui récupéré en

sortie. Grâce à la commande diff, nous avons pu confirmer la similarité de ces deux fichiers.

Le schéma ci-dessous représente l’algorithme général du processus du point de vue du PC :

Open files

Synchronization of PC and board program

Read the resquest and the number of data

Read file

Send data

Receive data

Write file

Close files

Figure 14: Algorithme d’échange de données (PC)

15 Projet avancé en systèmes embarqués – 2013/2014

La figure ci-dessous explique la gestion des échanges de données du point de vue du microprocesseur :

Synchronization of PC and board program

Read data

Process the data

Write data

Figure 15: Algorithme d’échange de données (Plasma)

On notera que l’étape « process the data » est vide dans cette partie, elle correspondra à l’étape du

décodage MP3 dans la partie suivante.

4.2.4.3 Test MP3 : envoi de données à partir de fichiers avec décodage

Nous avons précédemment testé la liaison Ethernet par lecture dans des buffers et dans un fichier MP3.

Maintenant, on souhaite tester le décodage. Pour cela, on envoie un fichier MP3 et on attend en sortie un fichier

PCM décodé.

Basiquement, les données en sortie du décodeur sont de taille beaucoup plus importante que celles en

entrée à cause du format PCM, qui est un format décompressé. Lors de l’exécution du programme, nous avons

donc été confrontés à un problème de corruption mémoire, en raison d’un dépassement de capacité de stockage

des buffers utilisés lors du décodage. Par manque de temps, nous n’avons pu résoudre ce problème.

16 Projet avancé en systèmes embarqués – 2013/2014

5 Conclusion

Ce projet s’est déroulé en différentes phases allant de la prise en main de l’architecture et du flot de

données jusqu’à l’implémentation et la modification d’une application complexe. Nous avons ainsi pu simuler et

implémenter des applications diverses en utilisant un SoPC basé sur un processeur softcore OpenCore Plasma.

Nous avons ainsi pu nous rendre compte qu’implémenter matériellement des fonctions peut se révéler

être très bénéfique en termes de performances diverses pour un temps de développement relativement court.

Le flot de conception étant relativement complexe, nous avons passé plusieurs séances afin de le

maîtriser. Cette maitrise a été obtenue après l’implémentation des applications AES et Golay. Nous nous sommes

ensuite penchés sur la modification et l’implémentation du décodeur MP3. Nous avons pour cela eu à redéfinir

certaines fonctions en langage C, profiler l’application afin de choisir les fonctions à implémenter matériellement

puis, une fois les modifications apportées, porter le décodeur MP3 sur la carte FPGA.

Cette dernière étape n’a pas pu être complétée à cause de problèmes de compilation inhérents au

décodeur lui-même. Avec du recul, nous aurions organisé notre approche du projet différemment. En effet, une

fois la maîtrise du flot de conception acquise suite à l’implémentation des applications simples, nous aurions pu

diviser le travail de manière à ce qu’un groupe s’occupe du portage du décodeur MP3 sur la carte alors que l’autre

groupe aurait réalisé les optimisations matérielles.

Néanmoins, ce projet nous a permis d’acquérir une vision plus large d’un système SoPC, de se rendre

compte des optimisations qu’il est possible d’apporter à une application et d’apprécier un flot de conception

complexe. Enfin, ce projet nous a permis d’utiliser les connaissances que nous avons apprises durant notre

scolarité à l’ENSEIRB-MATMECA, notamment dans les domaines des systèmes embarqués et de l’informatique

orientée vers l’électronique.

17 Projet avancé en systèmes embarqués – 2013/2014

6 Annexes

6.1 Exemple de test ALU

6.1.1 AES

computation : process (INPUT_1)

variable x_in : std_logic_vector(31 downto 0);

variable y1_in : std_logic_vector(31 downto 0);

variable y2_in : std_logic_vector(31 downto 0);

variable y3_in : std_logic_vector(31 downto 0);

variable y4_in : std_logic_vector(23 downto 0);

variable y5_in : unsigned(31 downto 0);

variable x_out : std_logic_vector(31 downto 0);

begin

--#define Xtime(x) ((((x) & 0x7f7f7f7f) << 1) ^ ((((x) & 0x80808080) >> 7) * 0x0000001b))

x_in := INPUT_1;

y1_in := x_in and x"7f7f7f7f";

y2_in := y1_in(30 downto 0) & '0';

y3_in := x_in and x"80808080";

y4_in := y3_in(31 downto 8);

y5_in := unsigned(y4_in) * to_unsigned(27,8);

x_out := y2_in xor std_logic_vector(y5_in);

OUTPUT_1 <= STD_LOGIC_VECTOR( x_out );

end process;

6.1.2 Golay

computation : process (INPUT_1, INPUT_2)

variable pattern : std_logic_vector(31 downto 0);

variable aux : std_logic_vector(31 downto 0);

variable rTemp1 : UNSIGNED(31 downto 0);

variable rTemp2 : UNSIGNED(31 downto 0);

variable rTemp3 : UNSIGNED(31 downto 0);

variable rTemp4 : UNSIGNED(31 downto 0);

variable rTemp5 : UNSIGNED(31 downto 0);

variable rTemp6 : UNSIGNED(31 downto 0);

variable rTemp7 : UNSIGNED(31 downto 0);

variable rTemp8 : std_logic_vector(31 downto 0);

begin

-- u32 INST1(u32 pattern, u32 aux)

-- return (pattern ^ (aux/X11) * GENPOL);

pattern := INPUT_1;

aux := "00000000000" & INPUT_2(31 downto 11);

-- rTemp7 := UNSIGNED(aux) * TO_UNSIGNED(3189, 12);

rTemp1 := UNSIGNED(aux(20 downto 0) & "00000000000");

rTemp2 := UNSIGNED(aux(21 downto 0) & "0000000000");

rTemp3 := UNSIGNED(aux(25 downto 0) & "000000");

rTemp4 := UNSIGNED(aux(26 downto 0) & "00000");

rTemp5 := UNSIGNED(aux(27 downto 0) & "0000");

rTemp6 := UNSIGNED(aux(29 downto 0) & "00");

rTemp7 := rTemp1 + rTemp2 + rTemp3 + rTemp4 +rTemp5 + rTemp6 + UNSIGNED(aux);

rTemp8 := pattern xor STD_LOGIC_VECTOR( rTemp7(31 downto 0) );

OUTPUT_1 <= rTemp8;

end process;

18 Projet avancé en systèmes embarqués – 2013/2014

6.1.3 MULSHIFT32

computation : process (INPUT_1, INPUT_2)

variable rTemp1 : SIGNED(31 downto 0);

variable rTemp2 : SIGNED(31 downto 0);

variable rTemp3 : SIGNED(63 downto 0);

begin

rTemp1 := SIGNED( INPUT_1 );

rTemp2 := SIGNED( INPUT_2 );

rTemp3 := rTemp1 * rTemp2;

OUTPUT_1 <= STD_LOGIC_VECTOR(rTemp3 (63 downto 32));

end process;

6.1.4 CLZ

computation : process (INPUT_1, INPUT_2)

variable rTemp1 : std_logic_vector(31 downto 0);

variable rTemp2 : UNSIGNED(31 downto 0);

variable rTemp3 : UNSIGNED(31 downto 0);

variable i : integer;

begin

if INPUT_1(31) = '1' then rTemp3:=to_unsigned(0,32);

elsif INPUT_1(30) = '1' then rTemp3:=to_unsigned(1,32);

elsif INPUT_1(29) = '1' then rTemp3:=to_unsigned(2,32);

elsif INPUT_1(28) = '1' then rTemp3:=to_unsigned(3,32);

elsif INPUT_1(27) = '1' then rTemp3:=to_unsigned(4,32);

elsif INPUT_1(26) = '1' then rTemp3:=to_unsigned(5,32);

elsif INPUT_1(25) = '1' then rTemp3:=to_unsigned(6,32);

elsif INPUT_1(24) = '1' then rTemp3:=to_unsigned(7,32);

elsif INPUT_1(23) = '1' then rTemp3:=to_unsigned(8,32);

elsif INPUT_1(22) = '1' then rTemp3:=to_unsigned(9,32);

elsif INPUT_1(21) = '1' then rTemp3:=to_unsigned(10,32);

elsif INPUT_1(20) = '1' then rTemp3:=to_unsigned(11,32);

elsif INPUT_1(19) = '1' then rTemp3:=to_unsigned(12,32);

elsif INPUT_1(18) = '1' then rTemp3:=to_unsigned(13,32);

elsif INPUT_1(17) = '1' then rTemp3:=to_unsigned(14,32);

elsif INPUT_1(16) = '1' then rTemp3:=to_unsigned(15,32);

elsif INPUT_1(15) = '1' then rTemp3:=to_unsigned(16,32);

elsif INPUT_1(14) = '1' then rTemp3:=to_unsigned(17,32);

elsif INPUT_1(13) = '1' then rTemp3:=to_unsigned(18,32);

elsif INPUT_1(12) = '1' then rTemp3:=to_unsigned(19,32);

elsif INPUT_1(11) = '1' then rTemp3:=to_unsigned(20,32);

elsif INPUT_1(10) = '1' then rTemp3:=to_unsigned(21,32);

elsif INPUT_1(9) = '1' then rTemp3:=to_unsigned(22,32);

elsif INPUT_1(8) = '1' then rTemp3:=to_unsigned(23,32);

elsif INPUT_1(7) = '1' then rTemp3:=to_unsigned(24,32);

elsif INPUT_1(6) = '1' then rTemp3:=to_unsigned(25,32);

elsif INPUT_1(5) = '1' then rTemp3:=to_unsigned(26,32);

elsif INPUT_1(4) = '1' then rTemp3:=to_unsigned(27,32);

elsif INPUT_1(3) = '1' then rTemp3:=to_unsigned(28,32);

elsif INPUT_1(2) = '1' then rTemp3:=to_unsigned(29,32);

elsif INPUT_1(1) = '1' then rTemp3:=to_unsigned(30,32);

elsif INPUT_1(0) = '1' then rTemp3:=to_unsigned(31,32);

else rTemp3:=to_unsigned(32,32);

end if;

OUTPUT_1 <= STD_LOGIC_VECTOR( rTemp3 );

end process;

19 Projet avancé en systèmes embarqués – 2013/2014

6.2 Exemple de test coprocesseur

6.2.1 MADD64

architecture logic of coproc_1 is

signal x : unsigned(31 downto 0);

signal y : unsigned(31 downto 0);

signal sum : unsigned(63 downto 0);

signal result : unsigned(31 downto 0);

type Etat is (Init, Set_x, Set_y, Set_sum1, Set_sum2, Ret1, Ret2);

signal etat_present, etat_futur : Etat;

begin

calcul_etat_future : process(etat_present,INPUT_1_valid)

begin

case etat_present is

when Init => etat_futur <= Set_x;

when Set_x => if INPUT_1_valid = '1' then

etat_futur <= Set_y;

else

etat_futur <= etat_present;

end if;

when Set_y => if INPUT_1_valid = '1' then

etat_futur <= Set_sum1;

else

etat_futur <= etat_present;

end if;

when Set_sum1 => if INPUT_1_valid = '1' then

etat_futur <= Set_sum2;

else

etat_futur <= etat_present;

end if;

when Set_sum2 => if INPUT_1_valid = '1' then

etat_futur <= Ret1;

else

etat_futur <= etat_present;

end if;

when Ret1 => if INPUT_1_valid = '1' then

etat_futur <= Ret2;

else

etat_futur <= etat_present;

end if;

when Ret2 => if INPUT_1_valid = '1' then

etat_futur <= Ret1;

else

etat_futur <= etat_present;

end if;

when others => etat_futur <= etat_present;

end case;

end process;

changement_etat : process(clock)

begin

if clock'event and clock = '1' then

if reset = '1' then

etat_present <= Init;

x <= (others => '0');

y <= (others => '0');

sum <= (others => '0');

else

etat_present <= etat_futur;

if etat_present = Set_x then x <= unsigned(INPUT_1); end if;

if etat_present = Set_y then y <= unsigned(INPUT_1); end if;

if etat_present = Set_sum1 then sum(31 downto 0) <= unsigned(INPUT_1); end if;

if etat_present = Set_sum2 then sum(63 downto 32) <= unsigned(INPUT_1); end if;

end if;

end if;

end process;

20 Projet avancé en systèmes embarqués – 2013/2014

calcul_sortie : process(etat_present, sum, x, y)

variable tmp0 : unsigned(63 downto 0);

variable tmp1 : unsigned(63 downto 0);

begin

tmp0 := x*y;

tmp1 := sum + tmp0;

case etat_present is

when Ret1 => result <= tmp1(63 downto 32);

when Ret2 => result <= tmp1(31 downto 0);

when others => result <= (others => '1');

end case;

end process;

OUTPUT_1 <= std_logic_vector(result);

end; --architecture logic

6.3 Redéfinition des fonctions C

6.3.1 fopen

FILE* fopen(const char *path, const char *mode){

if(mode[0]=='r' || mode[0]=='R')

return FIFO_IN_DATA_READ;

else if (mode[0]=='w' || mode[0]=='W')

return FIFO_OUT_DATA_WRITE;

else

return 0;

}

6.3.2 fclose

int fclose(FILE *fp){

return 0;

}

6.3.3 fread

int fread(void *ptr, int size, int nmemb, FILE *stream){

unsigned char* buf = ptr;

unsigned int nb_octet_lu = size*nmemb;

unsigned int octet_recus = 0;

unsigned char value;

puts("fread...\n");

o_write( FIFO_IN_DATA_READ ); // ON DEMANDE LE TRANSFERT DE DONNEES

o_write( nb_octet_lu ); // ON INDIQUE LE NOMBRE DE DONNES

while(octet_recus < nb_octet_lu){

while( !i_valid() );

value = (unsigned char)i_read();

// my_printf ("test => ", value);

buf[octet_recus++] = value;

// my_printf("octet ",octet_recus);

}

puts("stop fread...\n");

return octet_recus;

}

21 Projet avancé en systèmes embarqués – 2013/2014

6.3.4 fwirte

int fwrite(const void *ptr, int size, int nmemb, FILE *stream){

unsigned char* buf = ptr;

unsigned int nb_octet = size*nmemb;

unsigned int octet_transmis = 0;

o_write( FIFO_OUT_DATA_WRITE );

o_write( nb_octet );

while(octet_transmis<nb_octet){

while( o_full() != 0 );

o_write( buf[octet_transmis++] );

}

return octet_transmis;

}

6.3.5 memset

void *memset(void *s, int c, int n){

puts(" - memset start\n");

unsigned char* buf = (unsigned char*)s;

int i;

for(i=0;i<n;i++)

buf[i] = (unsigned char)c;

puts(" - memset stop\n");

return (void*)s;

}

6.3.6 strcmp

int strcmp(const char *s1, const char *s2){

int i=0;

int ret = 0;

while(ret == 0){

if(s1[i]=='\0' || s2[i]=='\0')

return (s1[i] -s2[i]);

ret = (int)(s1[i] - s2[i]);

i++;

}

return ret;

}

6.3.7 memmove

void *memmove(void *dest, const void *src, int n){

return (memcpy(dest,src,n));

}

6.3.8 memcpy

void *memcpy(void *dest, const void *src, int n){

puts(" - memcpy start\n");

int i =0;

unsigned char *d = (unsigned char)dest;

unsigned char *s = (unsigned char)src;

while(i<n){

d[i] = s[i];

i++;

}

puts(" - memcpy stop\n");

return dest;

}