Factorisation d`instructions, et ajout d`instructions Exercice 1

publicité
Université de Bordeaux
Licence STS
ARCHITECTURE DES ORDINATEURS
Projet :
Factorisation d’instructions, et ajout d’instructions
N.B. : - Vous devez rendre, pour la date indiquée par vos enseignants, un rapport détaillant le travail
que vous avez effectué en binôme. Les noms du binôme seront clairement indiqués sur la
page de garde du rapport. Vous adjoindrez à ce rapport les fichiers du simulateur que vous
aurez modifiés.
- L’ensemble sera envoyé par courriel à votre chargé de TD, sous forme d’un fichier archive de
nom : projet_nom1 _nom2.tgz .
- Comme la notation sera basée sur les informations fournies dans le rapport, celui-ci doit être
complet, bien que concis.
- N’hésitez pas, en particulier, à l’illustrer des fragments de code que vous avez modifiés, afin
de bien montrer la façon dont vous avez résolu les différents problèmes.
- La robustesse de votre approche sera également évaluée à la lumière des jeux de test que vous
aurez utilisés. Ceux-ci sont donc doublement importants, puisqu’ils vous serviront également
à tester votre approche au cours de son développement.
- À la fin du dernier TP, il y aura un petit questionnaire à remplir individuellement pour
ajouter une partie individuelle à la note de projet.
L’objectif de ce projet est d’étendre le jeu d’instructions du processeur y86.
Le projet est en deux étapes, qui sont en fait indépendantes. En premier lieu, il s’agira de factoriser
certaines instructions, pour libérer des numéros d’opcodes. Ensuite, on ajoutera le support de l’instruction
loop Si la première partie n’est pas faite, on pourra réutiliser simplement l’opcode de leave.
Exercice 1 : De la place dans les opcodes
La première étape est de faire de la place dans les opcodes y86. Par construction, seuls 16 opcodes
sont disponibles, et ils sont déjà tous pris ! Pour libérer des numéros, nous allons factoriser certaines
instructions très similaires, en complexifiant un petit peu le code HCL.
Factorisation de mrmovl avec rmmovl
mrmovl et rmmovl sont en fait très similaires : le calcul d’adresse et le même, la seule chose qui change,
c’est la direction des lecture/écriture. On peut donc les factoriser en un même icode, ifun étant utilisé
pour distinguer quelle direction est voulue.
Question 1
Dans misc/isa.h, dans l’enum itype_t, renommez I_MRMOVL en I_FREE1, et ajoutez à la place :
#define I_MRMOVL I_RMMOVL
afin que le reste du code utilise le même opcode pour mrmovl et rmmovl.
Vous remarquerez par contre que le fichier misc/isa.c ne compile plus. Ce fichier contient l’implémentation en langage C du processeur y86. Du fait que nous avons identifié I_MRMOVL avec I_RMMOVL, le
constructeur switch...case ne fonctionne plus, car il possède deux cas identiques. Mettez simplement en
commentaire (entre #if 0 et #endif) l’ensemble du case I_MRMOVL, puisque nous n’utilisons pas cette
implémentation C.
1
Il nous faut enfin distinguer les deux instructions à l’aide de ifun, il faut pour cela modifier instruction_set
dans misc/isa.c : pour MRMOVL, on passe 1 au lieu de 0 comme deuxième paramètre de HPACK.
Puisque l’on a changé les opcodes, il faut recompiler tous les fichiers .yo pour utiliser la nouvelle
numérotation ! Pour simplifier, lancez make clean dans le répertoire sim, puis make pour tout recompiler.
Compilez un programme qui contient à la fois l’instruction rmmovl et mrmovl, pour vérifier en lisant le
fichier .yo qu’elles utilisent désormais le même icode, avec pour l’une if un = 0 et pour l’autre if un = 1.
Question 2
Il s’agit maintenant de corriger le fichier HCL. En effet, puisque MRMOVL et RMMOVL sont désormais
confondus, il s’agit maintenant de les distinguer dans les quelques cas où ils ne doivent pas avoir le
même comportement. Notamment, il faut indiquer à l’étage mémoire dans quel cas il faut lire et dans
quel cas il faut écrire. Par exemple, pour mem_read, on enlève MRMOVL de la syntaxe icode in, et l’on
ajoute || (icode == MRMOVL) à droite, et du coup à l’intérieur de la parenthèse on peut ajouter comme
condition que ifun doit être égal à 1. Complétez de même mem_write et dstM.
Question 3
Il faut également corriger la version pipelinée. Les modifications sont les mêmes, il faudra simplement
aussi ajouter la définition de intsig D_ifun
(bonus) Factorisation de push/pop/call/ret
push/pop/call/ret se ressemblent beaucoup. Il pourrait être utile d’en factoriser certaines, en les
distinguant simplement à l’aide du champ ifun auquel on donnerait des valeurs différentes. En regardant
les similitudes dans le code HCL, est-il intéressant de factoriser par paires push/pop et call/ret, ou
bien par paires push/call et pop/ret, ou bien encore le quadruplet entier push/pop/call/ret ?
Réalisez la factorisation.
Exercice 2 : Ajout de l’instruction loop
L’instruction loop label est équivalente à la combinaison des deux instructions isubl 1,%ecx ; jne label
sauf qu’elle ne modifie pas les cc. Elle décrémente donc le registre %ecx puis branche à l’étiquette label
si %ecx n’a pas atteint 0. Elle est donc typiquement utilisée pour effectuer des boucles for.
Assembleur
Tout d’abord, modifiez l’assembleur pour que cette instruction soit reconnue et générée par celui-ci.
Modifiez les fichiers yas-grammar.lex, misc/isa.h et misc/isa.c (seulement instruction_set) pour
ajouter la nouvelle instruction (on pourra s’inspirer de call), on définira un nouvel opcode I_LOOP, avec
un ifun égal à 0.
Vérifiez que l’assembleur compile correctement un code source de test que vous créerez pour l’occasion,
et que le code machine généré (lisez le fichier .yo généré) est bien le bon, avec le bon opcode et ifun=0,
et la constante de l’étiquette bien placée.
Séquentiel
Modifiez ensuite le fichier HCL pour implémenter loop : corrigez d’abord la version séquentielle, il
faut d’abord ajouter une ligne intsig LOOP et une ligne intsig RECX en vous inspirant des autres, puis
traiter le cas LOOP dans les différents blocs.
Notez que pour new_pc, on n’utilisera pas le booléen Bch (car loop n’est pas censé modifier les cc),
mais valE != 0.
Testez votre implémentation en vérifiant bien dans chaque étage du processeur que le comportement
est bien celui qui est voulu.
2
Pipeliné
Il s’agit maintenant d’implémenter la version pipelinée de loop.
Branchement seul
Dans un premier temps, on se contente d’implémenter le cas où l’on branche : on prédit seulement
qu’on branche toujours, et on n’essaie pas de corriger, on ne se pose donc pas encore de question sur les
bulles. Il faut d’abord ajouter bien sûr les intsig, on peut alors traiter le cas LOOP dans les différents
blocs sauf f_pc, *_bubble et *_stall (mais il faut indiquer la prédiction "branche toujours" dans
new_F_predPC), l’implémentation est très similaire à ce qui a été fait en séquentiel.
Forward
Dans votre test, utilisez %ecx à l’intérieur de la boucle. A-t-on besoin d’ajouter du code de forward
pour que cela fonctionne correctement ?
Prédiction de branchement
Puisque nous n’avons pas touché à la prédiction de branchement (et donc on prédit toujours que
l’on branche), la boucle ne s’arrête pas... Il faut maintenant ajouter le traitement de la correction de
prédiction de branchement. Ajoutez son traitement dans *_bubble : inspirez-vous du traitement fait
pour JXX et de la condition que l’on a utilisée pour LOOP en version séquentielle.
Remarquez que cela ne fonctionne pas correctement : on détecte effectivement qu’il faut ajouter des
bulles, mais l’on ne rebranche pas à la bonne adresse. Corrigez donc les blocs new_E_valA et f_pc.
(bonus) LOOPE/LOOPNE
Les instructions loope et loopne se comportent comme loop, sauf qu’elles examinent également le
flag Z : loope ne branche que si ecx est différent de 0 et que Z vaut 1, et loopne ne branche que si ecx
est différent de 0 et que Z vaut 0.
Ajoutez le support des instructions loope et loopne, en choisissant soigneusement leur encodage
pour optimiser l’implémentation. Il faudra modifier la boîte bleue bcond, c’est dans seq/ssim.c et dans
pipe/psim.c, au niveau de l’appel à la fonction take_branch.
3
Téléchargement