Skip to content

Latest commit

 

History

History
1868 lines (1340 loc) · 60.6 KB

TD.org

File metadata and controls

1868 lines (1340 loc) · 60.6 KB

#+TITLE : Prise de notes TD 4I100 ARCHI1

Séance 1 : 24/09/2019

TD 1

La difficulté de la conception de MIPS, c’est de concevoir un jeu d’instructions qui tiennent toutes en 32 bits.

Exercice 1

lui R5, 0

Il y a plein de possibilités pour initialiser R5 à 0.

Apparemment, lui n’est pas la manière canonique de régler le problème. On préfère les opérations logiques bit par bit.

Xor R5, R5, R5

Exercice 2

Add R5, R6, R0
Or R5, R6, R0

Exercice 3

La bonne manière :

	  Ori R5, R0, 0x4567

La manière :

	  Addi R5, R0, 0x4567

Exercice 4

	    Lui R5, 0x4567
	    Ori R5, R5, 0xABCD

Exercice 5

Compilation à la main.

	  OR          R9, R4, R0
Boucle: LB          R8, 0(R9)
	  BEQ         R8, R0, fin
	  ADDIU       R9, R9, 1
	  J           Boucle
Fin:    SUB         R2, R9, R4
	  ADDIU       RS,R4,-1
Boucle: ADDIU       R2,R2,1
	  LB          R8,0(R2)
	  BNE         R8, R0, Boucle
	  SUB         R2,R2,R4

Exercice 6

Recopie de chaîne de caractères.

Solution Agon-Rambosson

	  OR          R8,R5,R0
	  OR          R9,R4,R0
Boucle: LB          R10,0(R8)
	  BEQ         R10,R0,fin
	  SB          R10,0(R9)
	  ADDIU       R9,R9,1
	  ADDIU       R8,R8,1
	  J           Boucle
Fin:    SB          R0,0(R9)
	  OR          R2,R4,R0

On a eu l’idée, probablement un peu difficile à mettre en place pour le moment, d’implémenter un cache dans les registres. Au lieu de charger un octet, on pourrait charger un mot entier, et traiter 8 bits par 8 bits le contenu du registre (un peu sale) : ça permettrait de diviser le nombre d’accès mémoire par 4.

Exercice 7

Ecrire la fonction strupper de la bibliothèque standart en assembleur.

On a une condition un peu complexe, qu’on ne pourra pas exprimer en une instruction, et qu’il faudra transformer :

char *strupper (char *str)
{
	  int i = 0;

	  while (str[i] != '\0') {

		  if ((str[i] >= 'a') && (str[i] <= 'z')) {
			  str[i] = str[i] - 'a' + 'A';
		  }

		  i++;
	  }
}

La condition de la ligne 7 du programme doit être traduite en un certain nombre de conditions qui se puissent transcrire en le moins d’instructions asm possible.

En particulier, les seules opérations de comparaison sont slt et ses dérivés : on doit exprimer les conditions sous la forme Reg < Immédiat, ou Reg < Reg.

On a :

(str[i] >= ‘a’) && (str[i] <= ‘z’) = C((str[i] < ‘a’) || (‘z’ < str[i]))

C(A || B) = C(A) && c(B)

On peut donc mettre dans un deux registres le résultat de la comparaison (str[i] < ‘a’) et (‘z’ < str[i]), en faire l’union dans un des deux registres, puis tester pour la différence avec 0 de ce registre : si le registre contient un entier différent de 0, on exécute à partir de l’étiquette endif.

	  ADDU        R2,R0,R4
	  ADDIU       R8,R0,'a'
	  ADDIU       R9,R0,'z'
loop:   LB          R10,0(R4)
	  SLT         R11,R10,R8
	  SLT         R12,R9,R10
	  OR          R12,R12,R11
	  BNE         R12,R0,endif
	  ADDIU       R10,R10,'A' - 'a'
	  SB          R10,0(R4)
endif:  ADDIU       R4,R4,1
	  BNE         R10,R0,loop

Exercice 8

On se propose d’écrire la fonction suivante en assembleur :

int *addvect(int *a, int *b, int *c, int size)
{
	  int i = 0;

	  while (size > 0) {
		  c[i] = 2 * a[i] + 3 * b[i];
		  i++;
		  size--;
	  }

	  return c;
}

La subtilité ici est de ne pas à se servir de mult, qui est une opération coûteuse.

La solution, c’est le décalage des bits à gauche : on se sert des propriétés du binaire.

Multiplication par 2 : décalage de tous les bits à gauche. Multiplication par 3 : multiplication par 2, puis addition avec l’antécédent.

On a une condition stricte, en revanche : Vu qu’on fait une multiplication par 2 et une par 3, on doit avoir les deux bits du poids fort à 0, sinon le résultat de la multiplication ne se laisse pas écrire dans les 32 bits du registre.

On part du principe que cette condition est remplie.

Voilà notre solution (un certain nombre d’erreurs du tableau ont été corrigées) :

	  OR          R2,R0,R6
loop:   BLEZ        R7,fin
	  LW          R8,0(R4)
	  LW          R9,0(R5)
	  SLL         R8,R8,1             #R8 contient 2a[i]
	  SLL         R10,R9,1            
	  ADDU        R10,R10,R9          #R10 contient 3b[i]
	  ADDU        R10,R10,R8          #R10 contient 2a[i] + 3b[i]
	  SW          R10,0(R6)
	  ADDIU       R6,R6,4
	  ADDIU       R4,R4,4
	  ADDIU       R5,R5,4
	  ADDIU       R7,R7,-1
	  J           loop
fin:

On se gardera les exercices bonus pour la suite.

Séance 2 : 01/10/2019

TD 1, suite

Aparté : les registres

Conventions utilisées par GNU Compiler Collection, pour MIPS

R0Registre qui vaut toujours 0
R1A ne pas utiliser, réservé à l’assembleur.
R2 - R3Valeur de retour de la fonction appelée (R3 est là pour les retours sur 64 bits)
R4 - R7Registre pour passer les 4 premiers paramètres de la fonction appelée (les éventuels suivants sont sur la pile)
R8 - R15Registres de travail non préservés à travers l’appel d’une fonction (flush à l’entrée d’une fonction)
R16 - R23Registres de travail préservés à travers l’appel d’une fonction
R24 - R25Comme R8 - R15
R26 - R27Ne doivent pas être utilisés par le compilateur
R28GP (Global pointer : pointeur vers les variables globales)
R29SP (Stack pointer : pointe sur la pile, là où les données non dynamiques sont stockées et lues)
R30Comme R16 - R23
R31Adresse de retour de la fonction appelante

La pile grandit vers le bas.

On doit mettre dans la pile les paramètres de la fonction

	  ADDIU       R29,R29,-(n*4)
	  SW          R4,16(R29)
	  SW          R5,12(R29)
	  SW          R6,8(R29)
	  SW          R7,4(R29)
	  SW          R8,0(R29)
	  JAL         @fonction

JAL fait deux choses :

  • Met PC + 4 dans R31
  • Il change le registre PC vers l’adresse passée en paramètre

On doit faire ça nous même :

  • Allouer (1 + nb(R à sauver) + nb(VarLoc)) * 4
  • Stocker les Registres à sauver
  • Stocker les variables locales

Restitution de la fonction :

  • On doit charger les registres qui auraient pu être écrasés, depuis la pile
  • On bouge le SP vers le haut, de la même quantité qu’on l’avait baissé avant
  • On saute à l’adresse contenue dans R31

On a un problème : le registre R8 ne contient pas de paramètre de la fonction appelée. Il est juste autre part dans la pile, il faut aller le chercher : en fait, on suppose gentiment que toutes une série d’opérations chiantes sont faites pour nous, mais pas toutes non plus : à un moment, on décide qu’on doit faire les opérations faites par le compilateur, à un autre, on décide que ce n’est pas la peine, sans logique apparente.

On a en fait toute une série d’instructions implicites dans les exercices LW pour charger les paramètres depuis la pile (ce qui requiert qu’on connaisse leur adresse) : a priori, le compilateur est capable de les retrouver, c’est lui qui a écrit le code assembleur qui les stockait en un endroit de la pile : il n’est pas compliqué pour lui de se rappeler d’où il les a mis.

Exercice 9

On prend un exemple, en supposant gentiment que les paramètres sont déjà dans les bons registres (on va quand même devoir lever cette hypothèse un moment.)

pgcd:   ADDIU       R29, R29,-4
	  SW          R31,0(R29)

loop:   BEQ         R4,R5,eloop
	  SLTU        R16,R4,R5
	  BEQ         R16,R0,else
	  SUB         R5,R5,R4
	  J           loop

else:   SUB         R4,R4,R5
	  J           loop

eloop:  OR          R2,R4,R0

eplg:   LW          R31,0(R29)
	  ADDIU       R29,R29,4
	  JR          R31

Exercice 10

Mon idée, très verbeuse, linéaire, occupant beaucoup de registres, aimablement corrigée et commentée par mes camarades :

tri:
	  ADDIU       R29,R29,-28
	  SW          R31,16(R29)

	  OR          R8,R0,R0

loop1:
	  SUB         R12,R8,R5
	  BGEZ        R12,eloop1

	  SLL         R15,R8,2
	  ADD         R15,R4,R15

	  LW          R10,0(R15)
	  ADDI        R9,R8,1
loop2:
	  SUB         R13,R9,R5
	  BGEZ        R13,eloop2
	  SLL         R24,R9,2
	  ADD         R24,R4,R24

	  LW          R14,0(R24)

	  ADDI        R9,R9,1
	  SUB         R25,R14,R10
	  BLEZ        R25,loop2

	  OR          R11,R10,R0
	  OR          R10,R14,R0
	  SW          R11,0(R24)

	  J           loop2

eloop2:
	  SW          R10,0(R15)
	  ADDI        R8,R8,1
	  J           loop1

eloop1:
	  OR          R2,R4,R0

eplg:
	  LW          R31,0(R29)
	  ADDIU       R29,R29,4
	  JR          R31

Une autre version, par la prof, de son propre aveu assez sale. Mais utilise moins de registres.

tri:
	  ADDIU       R29,R29,-20
	  SW          R31,16(R29)
	  OR          R2,R4,R0
	  BEQ         R5,R0,end_extloop   #On sort si le tableau est de taille 0
	  SLL         R12,R5,2            #Multiplication par 4
	  ADDU        R12,R12,R4          #Adresse fin de tableau

extloop:
	  LW          R8,0(R4)            # max=a[i]
	  ADDIU       R9,R4,4             # calcul adresse élément i+1
	  BEQ         R9,R12,end_intloop

intloop:
	  LW          R10,0(R9)           # charger a[i+1]
	  SLTU        R11,R8,R10          # max < a[j]
	  BEQ         R11,R0,endif
	  SW          R8,0(R9)            # On peut utiliser deux emplacements mémoire et un registre
	  OR          R8,R10,R0

endif:
	  ADDIU       R9,R9,4             # j++
	  BNE         R9,R12,intloop

end_intloop:
	  SW          R8,0(R4)
	  ADDIU       R4,R4,4
	  BNE         R4,R12,extloop

end_extloop:
	  LW          R31,16(R29)
	  ADDIU       R29,R29,20
	  JR          R31

TD 2

Aparté (ancien TD)

Rappel de pipeline

Partie I (Instruction Fetch) : On va chercher le mot mémoire et on le met dans le registre IR (Instruction Register)

Partie D (Decode) : On découpe l’instruction, on décode les numéros des registres concernés pour les identifier. Le PC est manipulé ici, car on sait où est la prochaine instruction : Soit PC++, soit saut à la bonne instruction.

Partie E (Execute) : On fait les calculs.

Partie M (Memory Access) : Seules les instructions Load et Store vont se servir de cet étage : on accède à la mémoire centrale en lecture et en écriture.

Partie W (Writeback) : A ce moment seulement le résultat éventuel de l’opération est mis dans le registre destination. On peut aussi modifier ici les registres comme R31.

Le fait que les valeurs soient écrites dans le registre seulement à la fin du pipeline pose tout un tas de problème :

loop:   LB          R9,0(R4)
	  BEQ         R9,R0,end_loop
	  ADDIU       R4,R4,1
	  ADDIU       R2,R2,1
	  J           strlen_loop

Ici, on a un problème :

R9 n’a sa bonne valeur qu’au moment du Writeback de la première instruction, qui arrive bien après le Decode de la deuxième instruction, moment où on a vraiment besoin que sa valeur soit bonne.

La solution naïve, c’est de geler l’instruction : mais si on fait ça, on peut ne pas savoir où on doit aller avant un petit moment, on doit retarder encore la prochaine instruction.

NOP : No operation : une espèce d’opération qui ne fait rien.

On a plusieurs manières de régler ce problème, chacune un peu imparfaite :

  • Solution matérielle : bypass, acheminer la solution où on en a besoin dès qu’on peut (coûteux)
  • Solution logicielle : réordonner les instructions
  • Solution matérielle : Exécution spéculative : on peut commencer à exécuter certaines parties, sans vraiment savoir où on doit aller

Séance 3 : 08/10/2019

TD 2

Pipeline (plus compliqué à dessiner). On essaie de simplifier les schémas autant qu’il est possible.

Aparté : notes sur le formalisme utilisé

On essaie autant qu’il est possible de tout pouvoir dessiner en caractères ASCII.

On utilisera les fonctionnalités des tableaux dans org-mode.

Les étages de pipeline sont dans une colonne chacun. On introduit des colonnes avant et après chaque étage, pour montrer les registres interétages.

Il est compliqué impossible de faire des flèches courbes. Pour cette raison, on notera >- une sortie de registre et -> et une entrée de registre. Si, dans un même étage de pipeline, plusieurs contenus de registre sont tranférés, on codera les flèches avec un chiffre : >-1 1-> (on comprend bien qu’on doit faire partir une flèche du registre de la première ligne pour aller dans le registre de la deuxième)

Les opérateurs et les multiplexeurs ne sont pas dessinés, ils sont donnés formellement en texte. Par exemple, pour expliquer qu’on a un opérateur d’incrémentation +4 entre le registre PC avant IFC et le registre PC après IFC, on ne peut pas faire un gros rond avec un +4 dedans. On se contentera d’écrire la chose parfaitement lisible suivante :

IFC
PC>-+4->PC

Ca marche aussi avec les multiplexeurs, qui ne font que vérifier des conditions logiques sur des contenus de registre connus de l’étage en question (donc déjà dessinés dans la colonne en question). Pour coder que l’opération du registre PC avant DEC vers le registre PC après RC dépend d’une condition dépendant des opérandes (dans le cas d’une instruction de type branch avec immédiat) on écrira :

BLTZAL (on n’a pas mis qu’on met ):

DEC
I_RI>-2
1->SOPER_RD
2->TOPER_RD
PC>- +4 (si 1 >= 2) ou +4 + (2)*4 (si 1 < 2) ->PC
R_V_CPU>-1

C’est un peu plus verbeux, mais encore parfaitement compréhensible (on pourrait raccourcir l’écriture avec un opérateur ternaire).

Les lignes du tableau donnent les registres : on est parfaitement capable de respecter le formalisme Pirouzien qui suppose que le registre à une ligne donnée est toujours bien le même registre. Vu que le nombre de registres dans un pipeline MIPS est fini et potentiellement connu, le nombre de lignes maximum du tableau pourrait théoriquement être ainsi donné.

Le seule vrai problème de ce formalisme est qu’il n’est pas possible d’y faire figurer le déroulement de plusieurs instructions. On est obligé de faire plusieurs tableaux (un schéma détaillé avec plus d’une instruction est illisible de toutes façons).

Exercice 1

On dessine l’instruction SLL rd, rs, rt

IFCDECEXEMEMWBK
->I_RI>–>I_RI
1->SOPER_RD>-1
ALU : Shift 2 by 1 ->RES_RE>-
2->TOPER_RD>-2
->DATA_RM>-
PC>-+4->PC>-+4->PC
R_V_CPU>-1,2->R_V_CPU

Exercice 2

Même question BLTZAL rs, label

IFCDECEXEMEMWBK
->I_RI>-2
1->SOPER_RD
->RES_RE>-
2->TOPER_RD
->DATA_RM>-
PC>-+4->PC>- +4 (si 1 >= 2) ou +4 + (2)*4 (si 1 < 2) ->PC
R_V_CPU>-1->R_V_CPU$31
>- +4 (inconditionné) ->IOPER_RD>-

La multiplication par quatre (en fait, toutes les puissances de 2) peut être fait dans l’étage décode.

Exercice 3

Une instruction nouvelle, qui n’existe pas dans le MIPS : BEQPI (branch if equal and post increment)

BEQPI RS,RT,label

C’est possible, on peut faire le branchement dans DEC et l’incrémentation se fait dans EXE.

C’est possible, l’incrémentation est inconditionnelle.

IFCDECEXEMEMWBK
->I_RI>-3
1->SOPER_RD
->RES_RE>-
2->TOPER_RD>-++
->DATA_RM>-
PC>-+4->PC>- +4 (si 1 != 2) ou +4 + (3)*4 (si 1 == 2) ->PC
R_V_CPU>-1 et 2->R_V_CPU

Exercice 4

Une instruction nouvelle, qui n’existe pas dans le MIPS : BEQPD (branch if equal and pre decrement)

BEQPD RS,RT,label

Pas possible : la décrémentation devrait être avant le branchement qui est fait dans l’étage DEC, donc dans l’étage DEC aussi, il faudrait y rajouter un additioneur, ce qui allongerait la durée de l’étage et donc de tous les étages.

A dire vrai, possible mais pas worth.

Exercice 5

ADD R3,R2,R1 ADD R3,R3,R1

A dire vrai, ces deux opérations sont comprimables en 1 :

ADD R3,R2,R1*2

On peut décaler la broche de R1 vers le poids fort de 1.

Mais là n’est pas la question.

IFCDECEXEMEMWBK
->I_RI>-
1->SOPER_RD>-1
ALU 1+2 ->RES_RE>-
2->TOPER_RD>-2
->DATA_RM>-
PC>-+4->PC>- +4 ->PC
R_V_CPU>-1 et 2->R_V_CPU
IFC+1DEC+1EXE+1MEM+1WBK+1
->I_RE
->I_RD>-
->I_RI>-
1->SOPER_RD>-1
ALU 1+2 ->RES_RE>-
2->TOPER_RD>-2
->DATA_RM>-
PC>-+4->PC>- +4 ->PC
R_V_CPU>-1 et 2->R_V_CPU

L’idée, c’est de récupérer le contenu de RES_RE de l’instant t pour le mettre dans SOPER_RD. En terme de matériel, ça requiert un multiplexeur avec RES_RE et SOPER_RD en entrée et l’additioneur en sortie.

Quelles sont les conditions du mutliplexeurs : on veut comparer RS de l’instruction t et RD de l’instruction t-1.

On devrait avoir la même chose pour l’autre opérande : Un multiplexeur avec RES_RE et TOPER_RD en entrée et l’additioneur en sortie.

Dans ce multiplexeur, on veut comparer RT de l’instruction t et RD de l’instruction t-1.

Exercice 6

ADD R0, R2, R11 ADD R3, R0, R11

Ici, on n’est pas censé avoir de problème : on ne peut pas écrire dans R0.

R0 est toujours à jour !!!!!, il ne peut jamais être modifié.

On doit préciser la condition du bypass :

Le bypass est déclenché sssi RS de l’instruction i égale RD de l’instruction i-1 et tous deux sont différents de 0. (on a le même pour le registre RT, on se doute)

Exercice 7

LW R3,0(R2) ADD R3,R3,R11

On a R3 disponible et à jour à la fin de l’étage M de l’instruction t. On en a besoin au début de l’étage E de l’instruction t+1.

On va avoir besoin d’une instruction de gel.

On va la dessiner, si on peut. (edit : on peut, une instruction de gel consiste en l’écrasement des valeurs des registres interétage par elles-mêmes)

IFCDECEXEMEMWBK
->I_RM
->I_RE>-
->I_RD>-
*->I_RI>-
1->SOPER_RD>-1
ALU 1+2 ->RES_RE>-
2->TOPER_RD
*->DATA_RM>-
3->IOPER_RD>-2
PC>-+4->PC>- +4 ->PC
R_V_CPU>-1 et 2->R_V_CPU
IFC+1DEC+1GELEXE +1MEM+1WBK+1
->I_RM
->I_RE>-
->I_RD>–>I_RD>-
->I_RI>-
1->SOPER_RD>–>SOPER_RD>-1
1+2->RES_RE>-RES
2->TOPER_RD>–>TOPER_RD>-2
->DATA_RM>-
PC>-+4->PC>- +4 ->PC
R_V_CPU>-1 et 2->R_V_CPU

Le cycle de gel est déclenché sssi (une partie masquée de) I_RD @t == (une partie masquée de) I_RE @t-1

En gros, on compare une partie bien précise de l’instruction stockée dans I_RD avec une partie bien précise de l’instruction stockée dans I_RE (qui concerne donc l’instruction précédente).

Quelle partie bien précise ? On suppose que le matériel sait faire le masque/filtre pour comparer bit à bit. C’est ce qui était entendu par l’expression (une partie masquée de)

Exercice 8

	  addiu       $2,$0,4
	  lui         $3,0x00c
	  add         $2,$2,$2
	  ori         $3,$3, 0x4568
	  lw          $2,0($3)
	  lbu         $2,0($2)
	  ori         $2,$2, 0x0001
	  bltzal      $2,suite
	  addu        $0,$0,$0
suite:
	  jr          $31
	  addu        $31,$31,-8

Dépendances :

  • La 3 de la 1
  • La 4 de la 2
  • La 5 de la 4 et de la 3
  • La 6 de la 5
  • La 7 de la 6
  • La 8 de la 7
  • La 10 dépend de la 8 (BLTZAL)
  • La 11 est exécutée no matter what, malgré le jump (delayed slot)

Schéma simplifié :

123456789101112131415161718192021
ADDIUIDEM >-2W
LUIIDEM >-2W
ADDID-> EMW
ORIID-> E >-1MW
LWID-> EM >-1W
LBUIDO-> EM >-1W
ORIIODO-> E >-1MW
BLTZALOIOO-> DE >-2M >-3W
ADDUIDEMW
JRI-> DEMW
ADDUI-> DE >-1M >-2W
JKIO-> DEMW
ADDUOI-> DEMW

Quel est le CPI de cette série d’instructions ?

D’après Karine, on commence à compter à partir de la sortie de la première instruction considérée (on part du principe que ce qui était avant concernait d’autres instructions, ce qui est vrai) jusqu’à la sortie de la dernière instruction.

#Cycles = 21 - 5 + 1 = 17 #Instruction = 13

CPI = 17/13

Exercice 9

On profite de cet exercice pour lister les bypass du pipeline MIPS. On définit l’ordre du bypass comme le nombre d’étages de pipeline que le bypass permet de traverser (ordre 2 : le bypass permet d’obtenir une information située 2 étages plus loin)

Ordre 1 : E@t -> E@t+1

123456
ORI R3, R4, 0x0001IDE >-1MW
LW R5, 0(R3)ID-> EMW

Ici, le bypass est du côté RS

Pour un bypass nécessaire en RT, on regardera par exemple :

	  ORI         $3,$4,0x0001
	  ADD         $5,$6,$3

Ordre 2 : M@t -> E@t+2

1234567
LW R3, 0(R5)IDEM >-2W
NOPIDEMW
ADD R6, R3, R0ID-> EMW

Pour un bypass nécessaire en RT, on regardera par exemple :

	  LW          $3,0($5)
	  NOP
	  ADD         $6,$0,$3

Ordre 2 : E@t -> D@t+2

1234567
ADD R3, R4, R5IDE >-2MW
NOPIDEMW
BEQ R3, R7, labelI-> DEMW

Pour un bypass nécessaire en RT, on regardera par exemple :

	  ADD         $3,$4,$5
	  NOP
	  BEQ         $7,$3,label

Ordre 3 : M@t -> D@t+3

12345678
LW R3, 0(R5)IDEM >-3W
NOPIDEMW
NOPIDEMW
BEQ R3, R7, labelI-> DEMW

Pour un bypass nécessaire en RT, on regardera par exemple :

	  LW          $3,0($5)
	  NOP
	  NOP
	  BEQ         $6,$3,label

On a bien vu les 8 (4*2) bypass du pipeline MIPS.

Exercice 10

[pour s’entraîner]

Séance 4 : 15/10/2019

TD 2, suite

Exercice 10

Daniela dessine le schéma simplifié du code corrigé non optimisé.

Le code corrigé non optimisé :

loop:
	  lb          $9,0($4)
	  beq         $9,$0,end_loop
	  nop
	  addiu       $4,$4,1
	  addiu       $2,$2,1
	  j           end_loop
	  nop         

Comment vraiment bien compter le nombre de cycles :

12345678910111213
IDEMW
IOODEMW
IDEMW
IDEMW
IDEMW
IDEMW
IDEMW

Soit je commence à 6, mais je dois aller jusqu’à 14 compris : 9 Soit je commence à 5, mais je dois aller jusqu’à 13 compris : 9

On a bien CPI = 9/7 et CPIutile = 9/5

Pareil avec les deux autres, à faire pour s’entraîner.

Exercice 11

loop:
	  lb          $8,0($4)
	  slt         $9,$8,$11
	  slt         $10,$12,$8
	  or          $10,$10,$9
	  bne         $10,$0,endif
	  nop
	  addi        $8, $8, 'A' - 'a'
	  sb          $8,0($4)
endif:
	  addiu       $4,$4,1
	  bne         $8,$0,loop
	  nop

Trouver les dépendances :

RAW de 3 à 2 RAW de 5 à 4 RAW de 6 à 5 RAW de 9 à 8

En fait, il s’agissait de trouver les dépendances, et pas seulement les dépendances qui introduisent des aléas dans le pipeline :

Donc :

RAW de 3 à 2 RAW de 5 à 4 RAW de 6 à 5 RAW de 9 à 8

Mais aussi

RAW de 4 à 2 (sur $8) RAW de 5 à 3 (sur $9)

RAW de 12 à 8 (sur $8) RAW de 2 à 11 (sur $4, si on part du principe que la boucle reprend)

On doit, lors de l’écriture des schémas simplifiés, traiter les différents résultats des flots de contrôle.

Dans notre cas, les instructions 8 et 9 sont exécutées ou non : on fera donc un schéma simplifié dans le cas où elles le sont et où elles ne le sont pas.

Elles le sont :

1234567891011121314151617
IDEM >-1,2W
IDO-> EM >-2W
IO-> DE >-1MW
OID-> E >-1MW
IO-> DEMW
OIDEMW
IDE >-1M >-1W
ID-> EMW
IDEMW
I-> DEMW
IDEMW

17 - 5 + 1 = 13

Elles ne le sont pas :

123456789101112131415
IDEMW
IDOEMW
IODEMW
OIDEMW
IODEMW
OIDEMW
IDEMW
IDEMW
IDEMW

15 - 5 + 1 = 11 (c’est bien ça, on doit rajouter les bypass)

CPI moyen (#cycles moyen / #instructions_moyen)

(13 * 30% + 11 * 70%) / (11 * 30% + 9 * 70%)

[on fera le calcul si ça nous amuse]

Exercice 12

Transformation du code :

_pgcd_loop:
	  sltu        $8,$4,$5
	  beq         $8,$0, _pgcd_else
	  nop
	  sub         $5,$5,$4
	  j           _pgcd_endif
	  nop
_pgcd_else:
	  sub         $4,$4,$5
_pgcd_endif:
	  bne         $4,$5, _pgcd_loop
	  nop

Les dépendances :

RAW de 3 à 2 (pour $8) RAW de 11 à 9 (pour $4) RAW de 11 à 5 (pour $5)

RAW de 2 à 9 (pour $4) (mais conditionnel)

Le schéma simplifié :

[fait au tableau, à refaire pour s’entraîner]

Le calcul du CPI et du CPI utile

[fait au tableau, à refaire pour s’entraîner]

TD3 : optimisation de code, pipeline, superpipeline

Exercice 1

On ne peut pas vraiment optimiser ce code assembleur :

[remettre le code assembleur]

Exercice 2

loop:
	  lbu         $8,0($4)
	  addu        $8,$8,$5
	  lbu         $9,0($8)
	  sb          $9,0($4)
	  addiu       $4,$4,1
	  bne         $4,$10,loop

Trouver les dépendances :

RAW de 3 à 2 RAW de 4 à 3 RAW de 5 à 4 RAW de 7 à 6

RAW de 2 à 6 (over the loop)

Schéma simplifié

1234567891011121314
IDEM >-1W
IDO-> E >-1MW
IOD-> EM >-1W
OIDOEMW
IODE >-1MW
IO-> DEMW
IDEMW

Calculer le CPI

Optimisation :

loop:
	  lbu         $8,0($4)
	  addiu       $4,$4,1
	  addu        $8,$8,$5
	  lbu         $9,0($8)
	  bne         $4,$10,loop
	  sb          $9,-1($4)        

Le store byte (les instructions store en général) sont les candidats prioritaires à l’inclusion dans le delayed slot.

CPI égale CPI_utile égale 1.

La démarche en général est assez peu systématisée : en gros on dessine les flots de dépendance entre les instructions, et on essaie de panacher pour éloigner les instructions qui crééent des aléas.

Exercice 3

On considère un pipeline un peu modifié.

a : Schéma détaillé

[à refaire]

b : Pourquoi pas de bypass devant MEM1

On part du principe que MEM1 est bien l’étage le plus long. Mettre un bypass devant MEM1 augmente l’étage déjà le plus long, donc augmente la durée du cycle.

c : Faire en sorte que la programme précédent tourne dans notre processeur

loop:
	  lbu         $8,0($4)
	  addu        $8,$8,$5
	  lbu         $9,0($8)
	  sb          $9,0($4)
	  addiu       $4,$4,1
	  bne         $4,$10,loop
	  nop
	  nop

d : schéma simplifié

[Fait au tableau, à refaire]

13 cycles pour une itération

1024 * 13 * 2 ns = 26624 ns

e : même question pour un autre processeur

Le processeur a un bypass en plus de MEM2 fin à MEM1 début, au prix d’un cycle un peu plus long (2,1 ms au lieu de 2 ms)

On va probablement montrer que le bypass ne vaut pas le coup.

[refaire le schéma simplifié]

12 cycles pour une itération

1024 * 12 * 2,1 = 25804 ns

(Ca ne vaut quand même pas le coup)

f : réordonnancement

La seule bonne manière :

  • On met le sb dans le dernier nop
  • On panache entre les flux de dépendance
loop:
	  lbu         $8,0($4)
	  addiu       $4,$4,1
	  addu        $8,$8,$5
	  lbu         $9,0($8)
	  bne         $4,$10,loop
	  nop
	  sb          $9,0($4)

Refaire le schéma simplifié :

g

Pareil pour PROC2, même ordonnancement

h

On déroule la boucle :

loop:
	  lbu         $8,0($4)
	  lbu         $12,1($4)
	  addu        $8,$8,$5
	  addu        $12,$12,$5
	  lbu         $9,0($8)
	  lbu         $13,0($12)
	  sb          $9,0($4)
	  sb          $13,1($4)
	  addiu       $4,$4,2
	  bne         $4,$10,loop
	  nop
	  nop

Optimisation :

loop:
	  lbu         $8,0($4)
	  lbu         $12,1($4)
	  addiu       $4,$4,2
	  addu        $8,$8,$5
	  addu        $12,$12,$5
	  lbu         $9,0($8)
	  lbu         $13,0($12)
	  bne         $4,$10,loop
	  sb          $9,-2($4)
	  sb          $13,-1($4)

Sans avoir besoin de faire le schéma simplifié, on a un CPI de 1.

Séance 5 : 22/10/2019

TD 3 : Suite

Exercice 4

Question a

[La difficulté vient de l’interprétation à donner à la question] [Il fallait comprendre : le compilateur doit-il insérer des delayed slots ?]

Oui, le compilateur va devoir insérer des delayed slots ?

Combien y a-t-il de delayed slot à insérer ? 3

Question b

On a des dépendances de données d’ordre 1 à 6. En effet, c’est seulement à partir de l’instruction i+7 qu’on est sûr qu’une opérande sera bien écrite dans le banc de registre avant sa lecture par l’étage DEC.

Par contre, pour les instructions i+1 à i+6 compris.

Question c

Ordre par ordre :

Ordre 1 : E2 fin -> E2 début (pas symétrique : 1 seul)

	  add         $5,$5,$6
	  sw          $5,0($4)

On veut l’adresse [réexpliquer]

Bypass sur RT seulement, l’instruction store est asymétrique

Ordre 2 : E2 fin -> E1 début

	  add         $3,$4,$5
	  nop
	  add         $7,$3,$6

Bypass sur RS et RT, l’instruction add est symétrique

Pas d’autre bypass d’ordre 2

Ordre 3 : M2 -> E2

	  lw          $3,0($5)
	  nop
	  nop
	  sw          $3,0($5)

Bypass sur RT, pas symétrique.

Ordre 3 : E2 -> D2

	  add         $3,$4,$5
	  nop
	  nop
	  add         $7,$3,$6

Bypass sur RT et RS, donc 2

Ordre 4 : M2 -> E1

	  lw          $4,0($5)
	  nop
	  nop
	  nop
	  add         $6,$4,$7

Bypass sur RT et RS, symétrique (2)

Ordre 4 : E2 -> D1

	  add         $5,$6,$7
	  nop
	  nop
	  nop
	  bne         $5,$8,label

Bypass sur RT et RS, symétrique (2)

Ordre 5 : M1 -> D1 (symétrique)

	  add         $3,$4,$5
	  nop
	  nop
	  nop
	  nop
	  bne         $3,$6,label

Ordre 5 : M2 -> D2 (symétrique)

	  lw          $3,0($4)
	  nop
	  nop
	  nop
	  nop
	  add         $7,$3,$5

Ordre 6 : M2 -> D1 (symétrique)

	  lw          $3,0($4)
	  nop
	  nop
	  nop
	  nop
	  nop
	  bne         $3,$5,label

16 pipelines [à recompter]

(Il y aurait toujours un exo avec un pipeline stupide : à refaire et à comprendre pour pouvoir refaire sur un pipeline arbitrairement débile)

d

Instructions qui produisent 1 cycle de gel.

Ordre 1

	  add         $3,$4,$5
	  add         $7,$3,$6

RAW, 1 cycle de gel

Ordre 2

	  add         $3,$4,$5
	  nop
	  bne         $3,$6,label

RAW, 2 cycles de gel

Ordre 3

On reprend le précédent, ça fait juste un cycle de gel en moins.

	  add         $3,$4,$5
	  nop
	  nop         
	  bne         $3,$6,label

RAW, 1 cycle de gel

Ordre 4

	  lw          $3,0($4)
	  nop
	  nop
	  nop
	  bne         $3,$6,label

Deux cycles de gel

Ordre 5

La même, enlève un cycle de gel

	  lw          $3,0($4)
	  nop
	  nop
	  nop
	  nop
	  bne         $3,$6,label

Un cycle de gel

e

loop:   
	  lw          $8,0($4)
	  bgez        $8,endif
	  nop
	  nop
	  nop
	  sub         $8,$0,$8
	  sw          $8,0($4)

endif:
	  addiu       $4,$4,4
	  bne         $4,$9,loop
	  nop
	  nop
	  nop
12345678910111213141516171819202122232425262728
lwI1I2D1D2E1E2M1M2 >-1W
bgezI1I200000-> D1D2E1E2M1M2W
nopI100000I2D1D2E1E2M1M2W
nop00000I1I2D1D2E1E2M1M2W
nopI1I2D1D2E1E2M1M2W
subI1I2D1D2E1E2 >-1M1M2W
swI1I2D1D2E1-> E2M1M2W
addiuI1I2D1D2E1E2 >-1M1M2W
bneI1I2000-> D1D2E1E2M1M2
nopI1000I2D1D2E1E2M1M2W
nop000I1I2D1D2E1E2M1M2W
nopI1I2D1D2E1E2M1M2W

g

Cette question suppose quasiment de refaire un schéma simplifié

12345678910111213141516171819202122232425262728
lwI1I2D1D2E1E2M1M2 >-1W
bgezI1I200000-> D1D2E1E2M1M2W
nopI100000I2D1D2E1E2M1M2W
nop00000I1I2D1D2E1E2M1M2W
nopI1I2D1D2E1E2M1M2W
addiuI1I2D1D2E1E2 >-1M1M2W
bneI1I2000-> D1D2M1M2W
nopI1000I2D1D2E1E2M1M2W
nopI1I2D1D2E1E2M1M2W
nopI1I2D1D2E1E2M1M2W

Calcul du nombre de cycles en cas de branchement échoué : 20 Calcul du nombre de cycles en cas de branchement réussi : 18 Nombre de cycle moyen : 19

Instructions brutes en cas de branchement échoué : 12 Instructions brutes en cas de branchement réussi : 10 Nombre d’instructions moyen : 11

Instructions utiles en cas de branchement échoué : 6 Instructions utiles en cas de branchement réussi : 4 Nombre d’instructions utiles moyen : 5

CPI moyen = 19 / 11 CPI utile moyen = 19 / 5

[Apparamment, un schéma simplifié ramène 2-3 points]

h

Optimisation :

loop:
	  lw          $8,0($4)
	  addiu       $4,$4,4
	  bgez        $8,endif
	  nop
	  nop
	  sub         $8,$0,$8            # Execution spéculative
	  sw          $8,-4($4)
endif:
	  bne         $4,$9,loop
	  nop
	  nop
	  nop

Cycles si branchement échoue : 15 Cycles si branchement réussit : 13

Exercice de partiel de 2012

1

On a 5 delayed slots.

2

On a des dépendances d’ordre 1 à 6.

3

Les différents bypass, ordre par ordre :

Ordre 1 : E2 fin -> E2 début (pas symétrique : 1 seul)

	  add         $5,$5,$6
	  sw          $5,0($4)

On veut l’adresse au début de l’étage M, mais on ne peut la récupérer qu’en E2 au plus tard.

Ordre 2 : E2 fin -> E1 début

	  add         $3,$4,$5
	  nop
	  add         $7,$3,$6

Bypass sur RS et RT, l’instruction add est symétrique

Ordre 3 : M2 -> E2

	  lw          $3,0($5)
	  nop
	  nop
	  sw          $3,0($5)

Bypass sur RT, pas symétrique.

Ordre 3 : E2 -> D2 (ou M1 -> E1)

	  add         $3,$4,$5
	  nop
	  nop
	  add         $7,$3,$6

Bypass sur RT et RS, donc 2 (opération add symétrique)

Ordre 4 : M2 -> E1

	  lw          $4,0($5)
	  nop
	  nop
	  nop
	  add         $6,$4,$7

Bypass sur RT et RS, symétrique (2)

Ordre 5 : M2 -> D2 (symétrique)

	  lw          $3,0($4)
	  nop
	  nop
	  nop
	  nop
	  add         $7,$3,$5

Ordre 6 : M2 -> D1 (symétrique)

	  lw          $3,0($4)
	  nop
	  nop
	  nop
	  nop
	  nop
	  add         $3,$3,$5

4

loop:
	  lw          $8,0($4)
	  lw          $9,0($4)
	  sub         $10,$8,$9
	  bgez        $10, endif
	  nop
	  nop
	  nop
	  nop
	  nop
	  sub         $10,$9,$8
endif:
	  sw          $10,0($5)
	  addu        $5,$5,4
	  addu        $4,$4,4
	  bne         $4,$6,loop
	  nop
	  nop
	  nop
	  nop
	  nop

schéma simplifié

[à refaire à la maison, pour s’entraîner]

5

[entraînement]

6

Optimisation sans déroulage :

loop:
	  lw          $8,0($4)
	  lw          $9,4($4)
	  addiu       $5,$5,4
	  addiu       $4,$4,4
	  sub         $10,$8,$9
	  bgez        $10,endif
	  nop
	  nop
	  nop
	  nop
	  nop
	  sub         $10,$9,$8
endif:
	  bne         $4,$6,loop
	  sw          $10,-4($5)
	  nop
	  nop
	  nop
	  nop

CPI et CPI utile [refaire les calculs]

[la ]

[la deuxième boucle optimisée]

loop:
	  lw          $8,0($4)
	  lw          $9,4($4)
	  addiu       $5,$5,4
	  addiu       $4,$4,4
	  sub         $10,$8,$9
	  bne         $4,$6,loop
	  slt         $11,$10,$0
	  sub         $12,$0,$11
	  xor         $10,$10,$12
	  add         $10,$10,$11
	  sw          $10,-4($5)

7

Déroulage de boucle. Dans le cas d’un else, il faut faire deux endif dans la boucle déroulée.

Version déroulée

loop:
	  lw          $8,0($4)
	  lw          $9,4($4)
	  sub         $10,$8,$9
	  bgez        $10,endif
	  nop
	  nop
	  nop
	  nop
	  nop
	  sub         $10,$9,$8
endif1:
	  sw          $10,0($5)
	  lw          $18,8($4)
	  sub         $19,$9,$18
	  bgez        $19,endif2
	  nop
	  nop
	  nop
	  nop
	  nop
	  sub         $19,$18,$9
endif2:
	  sw          $19,4($5)
	  addiu       $5,$5,8
	  addiu       $4,$4,8
	  bne         $4,$6,loop
	  nop
	  nop
	  nop
	  nop
	  nop

Version optimisée :

loop:
	  lw          $8,0($4)
	  lw          $9,4($4)
	  lw          $18,8($4)
	  addiu       $5,$5,8
	  addiu       $4,$4,8
	  sub         $10,$8,$9
	  sub         $19,$9,$18
	  bgez        $10,endif1
	  nop
	  nop
	  nop
	  nop
	  nop
	  sub         $10,$9,$8
endif1:
	  bgez        $18,endif2
	  nop
	  nop
	  nop
	  nop
	  nop
	  sub         $19,$18,$9
endif2:
	  bne         $4,$6,loop
	  sw          $10,-8($5)
	  sw          $19,-4($5)
	  nop
	  nop
	  nop

Calcul du CPI et du CPI utile.

Séance 6 : 19/11/2019

On traite le pipeline logiciel.

TD 3, Exercice 2, question d

Le code, avec le delayed slot :

loop:
	  lbu         $8,0($4)
	  addu        $8,$8,$5
	  lbu         $9,0($8)
	  sb          $9,0($4)

	  addiu       $4,$4,1
	  bne         $4,$10,loop
	  nop

On distingue le corps de la boucle des instructions de contrôle de la boucle.

Les trois dernières instructions sont les instructions de contrôle de boucle.

On a un cycle de gel entre lbu et addu, et entre lbu et sb.

Les trois étages du pipeline logiciel seront donc les suivants :

  • lbu
  • addu + lbu
  • sb

Réécrivons le code :

loop:
	  sb          $9,-3($4)           # E3(i-2)
	  addu        $8,$8,$5            # E2(i-1)
	  lbu         $9,0($8)
	  lbu         $8,-1($4)           # E1(i)

	  bne         $4,$10,loop         # Suppose une incrémentation du registre $4 dans le prologue
	  addiu       $4,$4,1

On prendra la peine de rajouter le remplissage, le vidage du pipeline.

Exercice 4

[On reprendra ça : pipeline logiciel avec flot de contrôle]

TD4

Exercice 1

Question a

Il y a exactement 16 bypass par opérande : les lister est trivial.

Question b

Dans quelles situations peut-on avoir un nop* ?

Question c

La classe n’a pas aimé mon formalisme. Alors qu’en vrai c’est archi clair.

Question d

[à refaire]

[on a fait une espèce de schéma détaillé des registres I_RI (le tampon d’instructions), I_RD, I_RE, I_RM. Pas sûr que ce soit très utile] [Pris en photo, à reregarder]

Exercice 2

Exercice 3

Séance 7 : 26/11/2019

TD 4

Séance 8 : 03/12/2019

TD 4, dernier exercice

[ Fait au tableau, à reprendre ]

TD 5, le cache

[ au papier, à reprendre ] [ jusqu’à fin exercice 2 ]

Séance 9 : 10/12/2019

Exercice 3

La fréquence du bus est de l’ordre du dixième de la fréquence du processus.

5 à 10 cycles pour que la mémoire trouve la donnée.

Le CPI qui tient compte de ça se calcule de la manière suivante :

CPI_1 = (#cyclesintrinsèques + #cyclessupplémentaireslecture + #cyclessupplémentairesécriture) / (#instructions)

On n’oubliera pas de compter l’instructions fetch dans les lectures.

On tombe sur 12,2 cycles par instructions [refaire le calcul].

C’est lamentable, on va mettre un cache associatif à deux niveaux.

Interlude tampon d’écriture postée (à reprendre).

Après le cache associatif : 2,72

Après le cache de niveau 2 : 1,69

Après le tampon d’écriture postée : 1,39

Exercice supplémentaire (2008)

Première partie, toute nulle.

La partie sur le cache introduit la notion de Pi-bus.

Le bus est pipeliné.

Un cycle d’arbitrage est obligatoire : le contrôleur de bus va choisir le maître qui a le droit d’écrire.

Puis un cycle d’adresse (l’adresse est envoyée par le maître).

Puis un cycle de données (la donnée est récupérée par le maître).

Un cycle pour mettre à jour le cache.

Un cycle pour répondre au processeur.

1 cycle pour détecter le miss : 1 Obtenir le grant : 6 On envoie 16 adresses, plus 1 dernier data : 17 Màj du cache : 1 Réponse processeur : 1

26 cycles par lecture en cas de miss.

Ecriture : Ecrire dans le buffer : 1 Nombre de cycles pour évacuer une écriture : 1 + 6 + 1 + 1 = 9 cycles 1 cycle par écriture si le tampon n’est pas plein. Si le tampon est plein, ça dépend de quand il sera vide : pas déterministe.

96 miss.

On avait 16 cycles par itération.

17,17 cycles par itération à cause des cycles.