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