Les jeux d’instructions MIPS
Exercice 1 - Traduction d'expressions arithmétiques Partie a/ - Addition et soustraction Le code assembleur MIPS fourni effectue des opérations arithmétiques de base. En analysant les commentaires inclus dans le code source, nous pouvons reconstituer l'expression de haut niveau (en langage C) correspondante.
D'après le document Les jeux d’instructions MIPS
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Programming, Math · DOCX · 5 pages
Exercice 1 - Traduction d'expressions arithmétiques
Partie a/ - Addition et soustraction
Le code assembleur MIPS fourni effectue des opérations arithmétiques de base. En analysant les commentaires inclus dans le code source, nous pouvons reconstituer l'expression de haut niveau (en langage C) correspondante.
Code fourni :
addu $t0, $s1, $s2 # $t0 = g + h
addu $t1, $s3, $s4 # $t1 = i + j
subu $s0, $t0, $t1 # f = (g+h)–(i+j)
Explication : Les variables sont assignées aux registres de la manière suivante :
| Variable C | Registre MIPS |
|---|---|
f |
$s0 |
g |
$s1 |
h |
$s2 |
i |
$s3 |
j |
$s4 |
L'expression mathématique finale correspond à : f = (g + h) - (i + j);
Parties b/ et c/ - Données manquantes
Le document source est endommagé ou incomplet ici : il n'y a aucune instruction ni question sous les étiquettes b/ et c/. Il est par conséquent impossible de formuler une réponse pour ces deux points.
Exercice 2 - Structures de contrôle et boucles
Cet exercice demande de mettre en correspondance des structures de contrôle en C avec leur traduction en assembleur MIPS.
Partie a/ - Structure conditionnelle If-Else
Code C : if (a == b) c = d + e; else c = d - e;
Code MIPS corrigé :
Le code source utilise else comme étiquette, ce qui est valide mais requiert les deux points : lors de sa définition.
bne $s0, $s1, label_else # Si a != b, on saute à la branche else
addu $s2, $s3, $s4 # Branche if : c = d + e
j exit # Fin de la condition, on saute la branche else
label_else:
subu $s2, $s3, $s4 # Branche else : c = d - e
exit:
# Suite du programme
Partie b/ - Condition logique ET (&&)
Code C : if (($s1 > 0) && ($s2 < 0)) {$s3++;};
Le source propose deux solutions. La deuxième est plus optimisée car elle utilise des conditions inverses pour sauter le bloc dès qu'une condition est fausse (évaluation de type court-circuit).
Solution optimisée :
blez $s1, next # Si $s1 <= 0, la condition globale est fausse, on saute
bgez $s2, next # Si $s2 >= 0, la condition globale est fausse, on saute
addiu $s3, $s3, 1 # Si on arrive ici, les deux conditions sont vraies : $s3++
next:
Partie c/ - Condition logique OU (||)
Code C : if (($s1 > $s2) || ($s2 > $s3)) {$s4 = 1;};
Ici, si la première condition est vraie, on doit exécuter le code sans vérifier la seconde.
bgt $s1, $s2, L1 # Si $s1 > $s2, la condition globale est vraie, on saute à L1
ble $s2, $s3, next # Si $s2 <= $s3, la condition globale est fausse, on saute à next
L1: li $s4, 1 # Le bloc "if" est exécuté
next:
Partie d/ - Comparaison non signée
Code C : if ($s1 <= $s2) {$s3 = $s4;}; (non signés)
Pour les entiers non signés, MIPS utilise les instructions terminant par u (comme bgtu pour Branch if Greater Than Unsigned).
bgtu $s1, $s2, next # Condition inverse : on saute si $s1 > $s2
move $s3, $s4 # $s3 = $s4
next:
Partie e/ - Condition multiple signée
Code C : if (($s3 <= $s4) && ($s4 > $s5)) {$s3 = $s4 + $s5;}; (signés)
bgt $s3, $s4, next # Si $s3 > $s4, échec de la 1ère condition, on saute
ble $s4, $s5, next # Si $s4 <= $s5, échec de la 2ème condition, on saute
addu $s3, $s4, $s5 # Exécution du bloc
next:
Partie f/ - Boucle Do-While (Copie de chaîne)
Code C : i = 0; do {target[i]=source[i]; i++;} while (source[i]!=0);
Code MIPS :
move $t0, $s0 # $t0 = pointeur source
move $t1, $s1 # $t1 = pointeur cible
L1: lb $t2, 0($t0) # Charge un octet depuis la source
sb $t2, 0($t1) # Stocke l'octet dans la cible
addiu $t0, $t0, 1 # Incrémente le pointeur source
addiu $t1, $t1, 1 # Incrémente le pointeur cible
bne $t2, $zero, L1 # Continue tant que le caractère copié n'est pas '\0'
Partie g/ - Boucle For (Somme d'un tableau)
Code C : sum = 0; for (i=0; i<n; i++) sum = sum + A[i];
L'instruction xor $t1, $t1, $t1 est une méthode classique et rapide pour mettre un registre à 0.
move $t0, $s0 # $t0 pointe sur A[i] (base du tableau)
xor $t1, $t1, $t1 # i = 0 ($t1)
xor $s2, $s2, $s2 # sum = 0 ($s2)
L1: lw $t2, 0($t0) # $t2 = A[i]
addu $s2, $s2, $t2 # sum = sum + A[i]
addiu $t0, $t0, 4 # Avance au mot suivant dans le tableau (4 octets)
addiu $t1, $t1, 1 # i++
bne $t1, $s1, L1 # Boucle tant que i != n
Exercice 3 - Copie inversée et somme d'un tableau
Ce programme parcourt un tableau de 5 mots (20 octets au total). Il lit le tableau source du début vers la fin, et écrit dans le tableau de destination de la fin vers le début. En même temps, il calcule la somme des éléments.
Analyse et explications :
- Le pointeur source
$t0est initialisé au début detab1. - Le pointeur cible
$t1est initialisé àtab2 + 16(le 5ème et dernier élément, car 4 mots précédents × 4 octets = 16 octets d'offset). - Le registre
$v1accumule la somme.
Code MIPS avec indentations standard :
.data
tab1: .word 10, 20, 30, 40, 50
tab2: .word 0, 0, 0, 0, 0
somme: .word 0
.text
main:
li $v1, 0 # Initialise la somme à 0
li $s1, 5 # Compteur de boucle = 5
li $s2, 4 # Valeur de décalage mémoire (4 octets par mot)
la $t0, tab1 # Pointeur sur le 1er élément de tab1
la $t1, tab2+16 # Pointeur sur le dernier élément de tab2
la $t2, somme # Pointeur pour stocker la somme à la fin
boucle:
lw $s0, 0($t0) # Lecture de tab1[i]
sw $s0, 0($t1) # Écriture dans tab2[5-1-i]
add $t0, $s2, $t0 # $t0 = $t0 + 4 (avance dans tab1)
sub $t1, $t1, 4 # Recule dans tab2
sub $s1, $s1, 1 # Décrémente le compteur de boucle
add $v1, $s0, $v1 # Ajoute la valeur lue à la somme globale
sw $v1, 0($t2) # Mise à jour de la mémoire pour 'somme'
bne $s1, $zero, boucle # Reboucle si le compteur n'est pas nul
# Affichage du résultat
li $v0, 1 # Code syscall pour print_int
move $a0, $v1 # Place la somme dans l'argument $a0
syscall
# Fin du programme
li $v0, 10 # Code syscall pour exit
syscall
Exercice 4 - Séparation des nombres pairs et impairs
Le principe mathématique utilisé ici pour déterminer la parité est le "ET binaire" (andi) avec la valeur 1.
- Si
nombre AND 1 == 0, le nombre est pair (le bit de poids faible est à 0). - Si
nombre AND 1 == 1, le nombre est impair (le bit de poids faible est à 1).
Code MIPS expliqué :
.data
tab1: .word 1, 2, 3, 4, 5, 6
tabpaire: .word 0, 0, 0, 0, 0
tabimpaire: .word 0, 0, 0, 0, 0
.text
main:
li $s2, 6 # Longueur du tableau initial
la $t0, tab1 # Pointeur de lecture
la $t1, tabpaire # Pointeur d'écriture pour les pairs
la $t2, tabimpaire # Pointeur d'écriture pour les impairs
boucle:
lw $s0, 0($t0) # Charge un nombre depuis tab1
andi $s1, $s0, 1 # Masque binaire pour isoler le dernier bit
beq $s1, $zero, paire # Si le bit est 0, c'est pair, saut vers 'paire'
# Bloc impair :
sw $s0, 0($t2) # Stocke dans tabimpaire
add $t2, $t2, 4 # Avance le pointeur impair
j next # Saute le bloc pair
paire:
sw $s0, 0($t1) # Stocke dans tabpaire
add $t1, $t1, 4 # Avance le pointeur pair
next:
add $t0, $t0, 4 # Avance le pointeur source
sub $s2, $s2, 1 # Décrémente le compteur d'éléments restants
bne $s2, $zero, boucle
li $v0, 10 # Fin de programme
syscall
Exercice 5 - Recherche de caractère dans une chaîne
L'objectif est de trouver la lettre 'b' dans la chaîne ch1. Si elle est trouvée, la fonction renvoie son index ; sinon, elle renvoie la longueur totale.
Corrections appliquées au code source :
- L'instruction
lw $a2,'b'est syntaxiquement incorrecte, on ne charge pas une constante littérale aveclw(Load Word) mais avecli(Load Immediate). - L'adresse de départ de la chaîne (
ch1) n'était pas passée en paramètre ($a0) dans le programme principal, empêchant la fonction de lire la chaîne. L'instructionla $a0, ch1a été ajoutée. - À la fin du main, le source contenait
move $a0, $v1pour l'affichage, ce qui affichait 0 puisque le résultat de la fonction est dans$v0. Cela a été corrigé enmove $a0, $s0.
Code corrigé et opérationnel :
.data
lch1: .word 7 # Correction de l'espace manquant
ch1: .asciiz "acgfvbh"
.text
main:
lw $a1, lch1 # Paramètre 2 : Longueur = 7
li $a2, 'b' # Paramètre 3 : Caractère à chercher (CORRIGÉ : li au lieu de lw)
la $a0, ch1 # Paramètre 1 : Adresse de la chaîne (CORRIGÉ : ajouté)
jal trouve_b # Appel de la procédure
move $s0, $v0 # Sauvegarde le résultat
li $v0, 1 # Syscall pour afficher un entier
move $a0, $s0 # Prépare l'entier pour l'affichage (CORRIGÉ : $s0 au lieu de $v1)
syscall
li $v0, 10 # Syscall pour exit
syscall
trouve_b:
li $v0, 0
li $t0, 0 # $t0 = index courant i = 0
loop:
bge $t0, $a1, exit # Si i >= longueur, on sort (retourne la longueur)
add $t1, $a0, $t0 # $t1 = adresse de base + offset i
lb $t2, 0($t1) # Charge le caractère courant
bne $t2, $a2, num # Si différent de 'b', passe au suivant
# Si trouvé :
move $v0, $t0 # Résultat = i
jr $ra # Retour prématuré au main
num:
add $t0, $t0, 1 # Incrémente l'index
j loop # Reboucle
exit:
move $v0, $t0 # Place la longueur totale en valeur de retour
jr $ra # Retour au main
Exercice 6 - Calcul de la factorielle (Récursif)
Ce code lit un entier depuis l'utilisateur et calcule sa factorielle en appelant de manière récursive la fonction fact.
Remarque de convention : Habituellement en MIPS, la valeur de retour est placée dans le registre $v0. Ce code source utilise de manière explicite le registre $3 (qui correspond à $v1) pour renvoyer le résultat. Nous conservons cette convention puisqu'elle est définie par la source.
Code MIPS :
.data
str1: .asciiz "Entrez un entier :"
str2: .asciiz "La factorielle est "
.text
main:
li $v0, 4 # Syscall pour print_string
la $a0, str1
syscall
li $v0, 5 # Syscall pour read_int
syscall # L'entier lu est dans $v0
move $a0, $v0 # Place l'entier lu dans $a0 (argument pour fact)
jal fact # Appel de la fonction fact (résultat retourné dans $3)
sortie:
li $v0, 4 # Syscall pour print_string
la $a0, str2
syscall
li $v0, 1 # Syscall pour print_int
move $a0, $3 # Place le résultat depuis $3 vers $a0
syscall
li $v0, 10 # Exit
syscall
fact:
bgt $a0, 1, recur # Si n > 1, on effectue l'appel récursif
li $3, 1 # Cas de base : fact(0) ou fact(1) = 1
jr $ra # Retourne au parent
recur:
sub $sp, $sp, 8 # Alloue 8 octets dans la pile
sw $ra, 0($sp) # Sauvegarde l'adresse de retour
sw $a0, 4($sp) # Sauvegarde le paramètre n courant
sub $a0, $a0, 1 # n = n - 1
jal fact # Appel récursif (résultat partiel placé dans $3)
lw $a0, 4($sp) # Restaure le n courant
mul $3, $a0, $3 # Multiplie le n courant par le résultat récursif : n * fact(n-1)
lw $ra, 0($sp) # Restaure l'adresse de retour
add $sp, $sp, 8 # Libère la pile
jr $ra # Retourne au parent
Exercice 7 - Suite de Fibonacci (Récursif)
Cette procédure fib calcule le n-ième terme de la suite de Fibonacci. Contrairement à la factorielle, elle requiert deux appels récursifs pour chaque étape (n-1 et n-2), ce qui nécessite de sauvegarder un registre supplémentaire ($s0) dans la pile pour conserver le résultat partiel du premier appel.
Note sur les instructions pseudo-MIPS : Le code utilise subi qui n'est pas une instruction MIPS matérielle native (MIPS n'a que addi), mais l'assembleur SPIM la traduit automatiquement en addi avec un nombre négatif.
Code MIPS :
fib:
subi $sp, $sp, 12 # Alloue 12 octets sur la pile
sw $a0, 0($sp) # Sauvegarde de n
sw $s0, 4($sp) # Sauvegarde de $s0 (va stocker fib(n-1))
sw $ra, 8($sp) # Sauvegarde de l'adresse de retour
bgt $a0, 1, gen # Si n > 1, cas récursif
move $v0, $a0 # Cas de base : si n=0 ou n=1, fib(n)=n
j rreg # Saute à la restauration des registres
gen:
subi $a0, $a0, 1 # Paramètre = n - 1
jal fib # Appel récursif pour fib(n-1)
move $s0, $v0 # Sauvegarde fib(n-1) dans $s0
subi $a0, $a0, 1 # Paramètre = n - 2 (puisque $a0 a déjà été décrémenté de 1)
jal fib # Appel récursif pour fib(n-2)
add $v0, $v0, $s0 # Résultat total = fib(n-2) (dans $v0) + fib(n-1) (dans $s0)
rreg:
lw $a0, 0($sp) # Restaure les paramètres originaux
lw $s0, 4($sp)
lw $ra, 8($sp)
addi $sp, $sp, 12 # Libère la pile
jr $ra # Retourne au parent
Méthode
Pour réussir ce type d'examen sur l'architecture et les instructions MIPS, il est crucial d'adopter une méthode de lecture et d'analyse pas-à-pas :
- Identifier les traductions classiques : Comprenez les motifs récurrents qui lient le langage de haut niveau à l'assembleur. Un bloc
if-elseimplique une condition de saut inversée (par exemple, si la condition C esta == b, on utilisebnepour sauter au code du "sinon"). Les boucles (for,while) nécessitent une phase d'initialisation avant une étiquette, un test de sortie, un incrément d'adresse et un saut inconditionnel ou conditionnel à la fin. - Gérer la mémoire (Tableaux et Adresses) : Chaque accès mémoire (avec
lwousw) nécessite un pointeur calculé. N'oubliez pas que dans une architecture 32 bits, un entier occupe un "mot" (Word), soit 4 octets. L'incrément de l'index dans un tableau (lei++du C) doit toujours se traduire par un incrément de 4 octets de l'adresse en MIPS. - Respecter l'usage des registres : Lors de l'appel de procédures, respectez les conventions d'appel. Les registres
$a0-$a3 servent aux arguments,$v0-$v1 à la valeur de retour. - Tracer la pile d'exécution : Pour les fonctions récursives (Exercices 6 et 7), la clé est la préservation du contexte (
$raet les variables locales). Assurez-vous d'allouer suffisamment d'espace avec$sp(sub $sp, $sp, espace), de sauvegarder les registres avant de faire un appeljal, puis de les restaurer exactement dans l'ordre inverse des adresses avant de désallouer la pile et de retourner (jr $ra).
Commentaires
Aucun commentaire pour le moment. Posez la première question.