Vous êtes sur la page 1sur 6

Exercices d'architecture

Denis Barthou, ENSEIRB 2010-2011

1. Conversions

• Convertir en binaire sur 8 bits, en complément à 2, les nombres suivants: -65, -70
• Poser et calculer en binaire sur 8 bits en complément à 2 le calcul -65-70. Le résultat est-il
positif, négatif ? Expliquez la valeur du résultat.
• Quelle est la valeur en décimal du nombre flottant sur 32 bits suivant ?
00100000011100000000000000000001
Vous pouvez l'écrire avec des puissances de 2.

2. Jeu d'instructions
On considère une machine à 16 registres pour représenter les entiers: r0 jusqu'à r15, et 16 registres
pour représenter les flottants: f0 jusqu'à f15. Son jeu d'instruction est donné par la table suivante,
avec ri, rj, rk pouvant être n'importe lequel des registres entiers, fi et fj, fk n'importe lequel des
registres flottants. r0 vaut toujours 0. Les registres sont tous des registres 32 bits.

Instruction Détail
ri ← rj OP rk avec OP une opération parmi
OP ri,rj,rk
add, sub, mul, div, and, or, xor.
fi ← fj FOP fk avec FOP une opération
FOP fi,fj,fk
flottante parmi addf, subf, mulf, divf.
move ri,rj ri ← rj
movef (ri,rj,4),fk Valeur à l'adresse ri+rj*4 ← fk
movef fi,(rj,rk,4) fi ← valeur à l'adresse rj+rk*4
move (ri),rj Valeur à l'adresse ri ← rj
move ri,(rj) ri ← valeur à l'adresse rj
Fait un branchement à l'adresse n+adresse
jeq ri,rj,n courante si ri == rj. Sinon continue l'exécution. n
est une constante entière signée sur 5 bits.

• Proposer un codage de toutes ces instructions qui minimise le nombre de bits nécessaires.

3. Traduction en assembleur MIPS


Traduire en assembleur MIPS le programme C suivant:

for (i=0; i<50; i++) {


if (x !=2)
if (y != 3)
x=x*y+A[i];
else y=x*A[i];
A[i+1] = A[i]+1;
}
On supposera que x,y et A correspondent à des labels en mémoire (des adresses). Les valeurs des
variables x et y seront placées et gardées dans des registres.
4. Exécution pipelinée
On considère le code suivant MIPS:

add $1, $2, $3


add $5, $2, $3
sub $12, $4, $3
sw ($2), $5
add $5, $12, $11
lw $8, ($5)

On supposera, comme en cours, que l'architecture MIPS est pipelinée, avec 5 étages (qu'on notera
IF, ID, EX, MEM, WB) et qu'une instruction seulement est commencée chaque cycle.
• Ecrire le diagramme de temps montrant à quel étage du pipeline se trouve chaque instruction
en fonction du cycle d'exécution (sans tenir compte des dépendances). Quelles sont les
dépendances entre instructions ?
• Corriger le diagramme précédent en supposant que l'architecture n'a pas de mécanisme de
forwarding. En combien de cycles ce programme s'exécute-t-il ?
• Même question, en supposant cette fois que le chemin des données dispose de forwarding
entre tous les étages du pipeline.

5. Pipelines superscalaires
On considère une architecture pipelinée avec 10 étages. Chaque étage est traversé en 1 cycle.
• Pour exécuter 800 instructions, en supposant que cela ne provoque pas de stall, combien de
cycles sont nécessaires pour leur exécution ?
• En supposant qu'une instruction sur 8 provoque un stall d'un cycle, en combien de cycles
s'exécutent les 800 instructions ? Quel pourcentage du temps total d'exécution est passé
dans les stalls ?
On considère maintenant que l'architecture est pipelinée sur 10 étages et superscalaire, prenant 4
instructions par cycle. Mêmes questions.

6. Caches
On considère un processeur avec un cache 2-associatif, write allocate, d'une taille de 16Ko, et des
lignes de 8 octets chacune. Le cache utilise une politique de remplacement LRU. On considère le
code suivant

for (i=0; i<1000; i++) A[i] = B[i] + B[i+2]

avec A et B des tableaux d'entiers sur 32 bits. Le tableau A commence à l'adresse 0 et le tableau B à
l'adresse 8000. On supposera que les accès se feront dans l'ordre: B[i], B[i+2] puis A[i] pour chaque
itération i.
• Montrez, pour les 9 premiers accès, quels sont les hits/miss sur les 2 premiers sets du cache.
• Combien de hit et de miss se produisent pendant l'exécution de la boucle ? Justifiez votre
réponse.
• Que se passe-t-il si le cache est direct map ?
• On ajoute maintenant un mécanisme de prefetch qui, lorsqu'il se produit un miss, charge la
ligne de cache suivante. Montrez, pour les 9 premiers accès, quels sont les hits/miss sur les 3
premiers sets du cache.
Correction des exercices d'architecture
Denis Barthou, ENSEIRB 2010-2011

1. Conversions
-65-70=-135, c'est un nombre en dehors de l'intervalle représentable en complément à 2 sur 8 bits,
sa valeur absolue est trop grande, il va y avoir un overflow.
-65 → 01000001 +1 = 10111111
-70 → 01000110 +1 = 10111010
Leur somme donne : 01111001. C'est un nombre positif à cause de l'overflow.
Le nombre flottant est
0 01000000 1110000000 0000000000 001
Son signe est positif, son exposant est 64-127=-63 et sa valeur est:
vaut (1+2-1+2-2+2-3+2-23).2-63

2. Jeu d'instructions
Chaque registre a besoin de 4 bits. Les instructions FOP, OP, movef (les deux types) ont donc 12
bits pour les registres. Ces instructions sont au nombre de 13, c'est à dire qu'il faut 4 bits de plus
pour pouvoir les distinguer entre elles par leur opcode. Ca fait en tout, au minimum, 16 bits pour
une instruction. Montrons que 16 bits suffisent pour coder toutes les instructions.

Sur les 16 valeurs possibles d'opcode sur 4 bits, 13 sont utilisées par les instructions précédentes.
Pour les autres instructions move (3), 8 bits sont nécessaires pour les registres. On peut donc
prendre pour elles le même opcode (0000 par exemple) et le compléter sur les 4 bits suivants.
Pour le jeq, 13 bits sont nécessaires pour coder les deux registres et la constante sur 5 bits. Ca veut
dire que jeq ne devra se distinguer des autres instructions que grâce aux 3 premiers bits de l'opcode.
Prenons par exemple pour jeq l'opcode 001 (ca revient à dire que pour les autres instructions, les 4
premiers bits d'opcode 0010 et 0011 ne sont plus autorisés).
On peut donc prendre pour les opérations:
add → 0001 sub → 0100 mul → 0101 div → 0110 and → 0111 or → 1000 xor → 1001
addf → 1010 subf → 1011 mulf → 1100 divf → 1101 , movef → 1110 et 1111 (pour les deux
formes).
3. Traduction en asm MIPS
add $t0,$0,50
add $t1,$0,$0
lw $t2,(x)
lw $t3,(y)
add $t4,$0,A
L: lw $t6,($t4) // $t6 = A[i]
sub $t7, $t2, 2
beq $t7,$0,Endif // x-2 == 0
sub $t5,$t3,3
beq $t5,$0,Else // y-3 ==0
mul $t2,$t2,$t3 // x = x*y
add $t2,$t2,$t4 // x = x+A[i]
j Endif
Else: mul $t3,$t3,$t6 // y = y*A[i]
Endif: add $t6,$t6,1 // $t6 = A[i]+1
add $t4,$t4,4
sw ($t4),$t6 // A[i+1] = A[i]+1
sub $t1,$t1,1
bne $t1,$t0, L

4. Pipeline
Dans cette correction, les diagrammes de temps ne sont pas réalisés.
1: add $1, $2, $3
2: add $5, $2, $3
3: sub $12, $4, $3
4: sw ($2), $5
5: add $5, $12, $11
6: lw $8, ($5)
Les dépendances sont 2 → 5 (WAW), 2 → 6 (RAW) , 2 → 4 (RAW), 3 → 5 (RAW), 5 → 6
(RAW). En pipelinant sans tenir compte des dépendances, la dépendance 2 → 5 est vérifiée
(dépendance entre étages WB), la 2 → 6 également (entre WB et ID 4 cycles après). Les autres
posent problème.

Sans tenir compte des dépendances, l'exécution prendrait 6+4=10 cycles.


Avec les dépendances, sans forwarding, les dépendances sont entre les étages WB (écriture du
registre) et ID (lecture du registre). Au mieux, il faut que l'écriture dans le registre se fasse au même
cycle que sa lecture.
2 → 4 contraint 1 cycle de stall (et règle aussi la 3 → 5).
5 → 6 nécessite 3 cycles de stall.
L'exécution prend donc 10+1+3 cycles.

Avec forwarding, les dépendances 2 → 4 et 3 → 5 ne nécessitent plus de stall. La sortie de l'étage


EX stockée dans EX/MEM puis MEM/WB le cycle suivant est propagée dans l'ALU, etage EX au
début du cycle d'après. La dépendance 5 → 6 (entre le EX et le MEM) ne nécessite pas non plus de
stall. La valeur de sortie de l'ALU est stockée dans EX/MEM, puis dans MEM/WB pour être
finalement propagée en entrée de la mémoire. L'exécution prend 10 cycles.

5. Pipelines superscalaires

• Pour exécuter 800 instructions, en supposant que cela ne provoque pas de stall, il faut
800+10-1 cycles, soit 809 cycles.
• En supposant qu'une instruction sur 8 provoque un stall d'un cycle, en combien de cycles
s'exécutent les 800 instructions ? 800*(1+1/8)+10-1 = 909 cycles.Quel pourcentage du
temps total d'exécution est passé dans les stalls ? A peu près 11% du temps.
On considère maintenant que l'architecture est pipelinée sur 10 étages et superscalaire, prenant 4
instructions par cycle. Ca prend alors 200+10-1 cycles, soit 209 cycles (sans stall). Avec stall, un
cycle sur 2, les 4 instructions lancées font un stall. Donc avec stall, il faut 200*(1+1/2)+9 = 309
cycles. Ca fait maintenant 32% du temps passé dans les stalls.
6. Caches
Pour un cache k-associatif, avec des lignes de cache de L octets et une taille totale de cache de T
octets, il y a T/(L*k) sets. Une adresse adr est placée dans une ligne du set numéro:
adr/L mod T/(L*k).
Combien de sets dans le cache ? Chaque ligne a 8 octets. 2 lignes par set (cache 2 associatif), donc
16 octets/set. Comme le cache fait 16Ko, il y a donc 1000 sets. L'adresse de A[i] est 0+4*i. Le set
dans lequel va A[i] est (0 + 4*i)/8 mod 1000. Le set dans lequel va B[i] est (8000+4*i)/8 mod 1000.
B[i] et B[i+2] ne sont pas dans le même set. A[i] et B[i] sont dans le même set.

Pour les 9 premières itérations,


Acces
B[0]: miss → B[0], B[1] mis dans une ligne du set 0,
B[2]: miss → B[2], B[3] mis dans une ligne du set 1
A[0]: miss → A[0], A[1] mis dans deuxieme ligne du set 0 (le cache est write allocate)
B[1]: hit
B[3]: hit
A[1]: hit
B[2]: hit
B[4]: miss → B[4], B[5] mis dans ligne du set 2
A[2]: miss → A[2], A[3] mis dans deuxieme ligne du set 1

Pour les itérations qui suivent, ca se répète:


pour une itération i paire, i=2k,
• B[2k] est dans le set numéro k, déjà accédé lors de l'itération i-2. L'accès B[i+2] est un miss
car dans le set k+1 (jamais accédé avant). B[2k+2] et B[2k+3] sont mis dans le set k+1.
• l'accès à A[i] est un miss car aucun élément de A n'est encore dans le set k. Il charge alors
A[2k] et A[2k+1] utilisé l'itération suivante.
Pour une itération i impaire, i=2k+1
• B[2k+1] est dans le set k, c'est un hit
• B[2k+2] est dans le set k+1, c'est un hit (placé dans ce set l'itération d'avant).
• A[2k+1] est un hit.
Donc pour chaque itération paire, on fait 2 miss. Pour chaque itération impaire, on fait 0 miss. Pour
1000 itérations, il y a donc 500*2 miss, plus les miss lors de la première itération (itération paire
avec 3 miss). Ca fait donc 1001 miss.
La boucle a 1000 itérations qui balaient les set 0 à 999 et lors des deux dernières itérations, on
accède à nouveau au set 0. Le comportement reste toutefois identique, car ce sont les données les
plus anciennement accédées (ici dans les lignes B[0], B[1] et A[0], A[1]) qui sont éjectées du cache
(LRU).

Si le cache est direct map, A[i] est alors dans la ligne de cache (0+4*i)/8 mod 2000. B[i] est dans la
ligne (8000+4*i)/8 mod 2000. A[i], B[i], B[i+2] sont tous sur des lignes de cache différentes. Même
comportement du point de vue des miss/hits que le cas 2-associatif.

Dans le cas du prefetch:


Acces
B[0]: miss → B[0], B[1] mis dans une ligne du set 0, B[2] et B[3] mis dans une ligne du set 1.
B[2]: hit
A[0]: miss → A[0], A[1] mis dans deuxieme ligne du set 0, A[2], A[3] dans la deuxieme ligne du
set 1
B[1]: hit
B[3]: hit
A[1]: hit
B[2]: hit
B[4]: miss → B[4], B[5] dans une ligne du set 2, B[6], B[7] dans une ligne du set 3
A[2]: hit
Le prefetch mis en place (attention, d'autres stratégies de prefetch sont possibles) évite de faire les
miss sur la ligne suivante → 2 fois moins de miss au total.

Vous aimerez peut-être aussi