Upload
ngohuong
View
213
Download
0
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;
}