Mémento architecture et assembleur MIPS
Adapté de : https://minnie.tuhs.org/CompArch/Resources/mips_quick_tutorial.html
Ce mémento décrit les bases de l'architecture MIPS et le langage assembleur accessible par exemple avec des simulateurs comme MARS ou SPIM.
On se limite aux fonctionnalités directement utiles dans le cours,
mais pour une information complète, on pourra consulter le menu Help intégré dans l'application MARS.
Types de données et Constantes littérales
Types de données
- Les instructions sont toutes sur 4 octets.
- Les données sont accessibles sous 3 formes : byte(1 octet), halfword(2 octets), word(4 octets).
- Un caractère utilise 1 octet de mémoire.
- Un entier utilise 4 octets de mémoire.
Constantes littérales
- Les nombres sont utilisés tel que, i.e. 42, ou aussi 0xbabe.
- Les caractères sont entourés par des simples quote, i.e. 'b'.
- Les chaînes de caractères sont entourées par des doubles quotes, i.e. "Une chaine".
- Les noms de label sont liés au charset de l'environnement JAVA et peuvent donc être en UTF8 ou autre, i.e. Матрёшка.
Structure d'un programme
Un fichier du langage assembleur MIPS est un fichier texte contenant des déclarations de données (data segment) et le code du programme (text segment). Les déclarations de données peuvent être intercalées dans le code du programme.
Les commentaires peuvent apparaître dans toutes les parties d'un fichier MIPS.
Un commentaire commence par le caractère # et s'étend jusqu'à la fin de la ligne.
L'indentation est libre pour le confort de lecture.
Les labels sont utilisés par l'assembleur pour identifier des adresses mémoires statiques :
Une section de déclaration déclare des noms de variable, initialise leur valeur, et réalise leur allocation mémoire dans la zone des données statiques.
Commentaires et Indentations
Les commentaires peuvent apparaître dans toutes les parties d'un fichier MIPS.
Un commentaire commence par le caractère # et s'étend jusqu'à la fin de la ligne.
L'indentation est libre pour le confort de lecture.
Labels
Les labels sont utilisés par l'assembleur pour identifier des adresses mémoires statiques :
- un label est identifié par un nom suivi du caractère :,
- dans les déclarations de données, les labels sont des adresses mémoire de variables,
- dans le code du programme, les labels sont des adresses de saut dans le code,
- dans tous les cas, les valeurs de ces adresses sont calculées automatiquement par le programme d'assemblage.
Déclarations de données
Une section de déclaration déclare des noms de variable, initialise leur valeur, et réalise leur allocation mémoire dans la zone des données statiques.
- La section est identifiée par la directive :
.data # éventuellement une adresse en argument
- Le format d'une déclaration de variable est :
name: storage_type value(, value)*
- Par l'exemple :
.data # Déclarations de variables à suivre var1: .word 666 # int var1 = 666; var2: .half 42 # short var2 = 42; array1: .word 4, 8, 0x10 # int[3] array1 = {4, 8, 16} array2: .byte 42, 'Z' # char[2] array2 = {'*', 'Z'} array3: .space 20 # 20 octets non initialisés : byte[20]/int[5]/byte[4][5] str1: .asciiz "str" # chaîne ASCII terminée par \0 str2: .asciiz "a\nb" # chaîne multi-ligne ASCII terminée par \0 str3: .ascii "bon" # Chaîne ASCII non terminée par \0 .ascii "jour" # Chaîne ASCII suite .byte 0 # et une terminaison nulle
- Avec .word et .half, les adresses seront
automatiquement alignées sur des mots de 4 octets ou 2 octets. Dans tous les cas, on peut
forcer l'alignement d'une variable sur une frontière de 2^n octets avec la directive .align n avant la déclaration de variable :
.align 3 # aligner la prochaine adresse sur un mot de 2^3 octets array4: .space 42 # l'adresse de array4 est un multiple de 8
Code du programme
- Le code du programme est identifiée par la directive :
.text
- Le code contient des instructions et des labels pour les sauts.
- Le point d'entrée pour l'exécution est donnée par le label main:.
- La terminaison de l'exécution est réalisée en fin de fichier ou utilise l'appel système exit (cf. plus loin).
Exemple de fichier MIPS
# Un canevas de programme assembleur MIPS
.data # Des déclarations de variables à suivre
hello: .asciiz "hi world!" # ma première variable MIPS
.text # Des instructions du programme à suivre
fonct: # label pour la fonction "fonct"
# ... des instructions ...
main: # Le point d'entrée du programme
# ... des instructions ...
.data # Des déclarations de variables à suivre
num: .word 0
Les registres MIPS
- 32 registres pour l'usage général (+ 32 registres pour les instructions flottantes).
- Dans l'assembleur, les noms de registre commencent par $ avec deux formes au choix :
- utilisant le numéro du registre, i.e. de $0 à $31,
- utilisant un nom conventionnel, i.e. $sp, $t1...
- 2 registres spéciaux Lo et Hi sont utilisés pour les résultats de multiplications ou de divisions. Ils ne sont accessibles qu'avec les instructions spéciales mfhi (Move From Hi) et mflo (Move From Lo).
- La pile à la "tête en bas" : l'adresse de sommet de pile décroit quand la pile grandit.
Numéro | Nom | Sous-titre | Description |
---|---|---|---|
0 | zero | zéro | toujours la valeur 0 |
1 | at | Assembler Temporary |
réservé par l'assembleur pour les pseudo-instructions |
2-3 | v0-v1 | Values | Résultats de fonctions ou évaluations d'expressions |
4-7 | a0-a4 | Arguments | Les 4 premiers arguments d'une fonction Valeur non préservée par un appel de fonction |
8-15 24-25 |
t0-t7 t8-t9 |
Temporaries | Registre de travail Valeur non préservée par un appel de fonction |
16-23 | s0-s7 | Saved values | Registre de travail Valeur préservée par un appel de fonction |
26-27 | k0-k1 | Kernel | Réservé pour interruptions et trap handler |
28 | gp | Global Pointer | Adresse de base pour les données statiques |
29 | sp | Stack Pointer | Adresse du sommet de pile |
30 | fp | Frame Pointer | Adresse de base du cadre d'appel d'une fonction Valeur préservée par un appel de fonction |
31 | ra | Return Address | Adresse de retour de fonction Valeur préservée par un appel de fonction |
Instructions
Format des instructions
Les instructions MIPS sont toutes codées sur 32 bits et se déclinent synthétiquement suivant 3 formes :
6 bits | 5 bits | 5 bits | 5 bits | 5 bits | 6 bits | |
---|---|---|---|---|---|---|
format R | Op Code | Reg 2 | Reg 3 | Reg 1 | Shift | SubCode |
format I | Op Code | Reg 2 | Reg 1 | Immediate 16 bits | ||
format J | Op Code | Adresse 26 bits |
En assembleur, le terme Immediate désigne une constante entière dont la valeur est codée dans l'instruction plutôt que passée dans un registre.
Instructions d'accès mémoire et chargement de registre
Ces instructions sont les instructions qui permettent le transfert entre la mémoire (RAM) et les registres. Les autres instructions MIPS (calcul, saut,...) travaillent uniquement avec les registres du processeur.
On inclut aussi dans cette section, les instructions réalisant la copie entre registres ou le chargement de registres avec des valeurs constantes ou des labels (adresses statiques).
lw $R0, RAM # Load word, copie 4 octets de RAM dans $R0
sw $R0, RAM # Store word, copie 4 octets de $R0 dans RAM
lb $R0, RAM # Load byte, copie 1 octet de RAM dans $R0 (signed low-order)
sb $R0, RAM # Store byte, copie 1 octet (low-order) de $R0 dans RAM
lbu $R0, RAM # Load byte unsigned copie 1 octet de RAM
# dans $R0 (0-extended low-order)
lui $R0, value # Load Upper Immediate copie la constante value dans $R0
# avec décalage de 16 bits
li $R0, value # Load Immediate, copie la constante value dans $R0
# pseudo-instruction suivant 16bits signé ou non ou 32 bits
la $R0, label # Load Address, copie l'adresse du label dans $R0
# pseudo pour "lui $1, label>>16 ori $R0, $1, label&0xFFFF"
move $R0, $R1 # copie le registre $R1 dans le registre $R0
# pseudo pour "addu $R0, $zero, $R1"
Référence indirecte et indexée à la mémoire (RAM)
Dans les instructions Load/Store, la référence à la mémoire utilise un format général RAM = value($R1), où $R1 est un registre contenant une adresse mémoire de base et value est une constante entière signée utilisée comme décalage (offset) par rapport à l'adresse de base. Par exemple, l'adresse notée -12($t1) a pour valeur RAM[$t1-12].
N.B. La notation 0($R1) peut être abrégée par ($R1).
Exemple
.data
var1: .word 20 # int var1=20
array1: .space 12 # int[3] array1
.text
start:
la $t0, array1 # $t0 = adresse de base du tableau
li $t1, 5 # $t1 = 5
sw $t1, ($t0) # array1[0] = $t1, adresse de array1[0]=$t0+0
li $t1, 13 # $t1 = 13
sw $t1, 4($t0) # array1[1] = $t1, adresse de array1[1]=$t0+4
la $t2, var1 # $t2 = adresse var1
lw $t1, ($t2) # $t1 = valeur de var1
# Pseudo possible : "lw $t1, var1"
sw $t1, 8($t0) # array1[2] = $t1, adresse de array1[2]=$t0+8
move $t3, $t1 # $t3 = $t1
Instructions de calcul
Les opérandes (souvent 3) sont des registres ou éventuellement des constantes (immediate).
# arithmétique
add $R0, $R1, $R2 # $R0 = $R1 + $R2; entiers avec signe
addi $R0, $R1, -42 # $R0 = $R1 + -42; entiers et constantes signés
addu $R0, $R1, $R2 # $R0 = $R1 + $R2; entiers non signés
addiu $R0, $R1, 666 # $R0 = $R1 + 666; entiers non signée
sub $R0, $R1, $R2 # $R0 = $R1 - $R2; entiers avec signe
subu $R0, $R1, $R2 # $R0 = $R1 - $R2; entiers non signés
mult $R1, $R2 # [Hi,Lo] = $R1 * $R2; [Hi,Lo] registre spécial 64 bits
div $R1, $R2 # Lo = $R1 / $R2, Hi = $R1 mod $R2;
mfhi $R0 # Move From Hi $R0 = Hi;
mflo $R0 # Move From Lo $R0 = Lo;
# Logique binaire
and $R0, $R1, $R2 # $R0 = Bitwise AND($R1, $R2)
or $R0, $R1, $R2 # $R0 = Bitwise OR($R1, $R2)
# Décalage bit
sll $R0, $R1, 5 # $R0 = shift left 5 bits from $R1 (= multiply 2^5)
srl $R0, $R1, 7 # $R0 = shift right 7 bits from $R1
# Comparaison ("Set if")
slt $R0, $R1, $R2 # Set on Less Than $R0 = 1 si $R1 < $R2, 0 sinon
sltu $R0, $R1, $R2 # slt unsigned $R0 = 1 si 0 <= $R1 < $R2, 0 sinon
seq $R0, $R1, $R2 # Set on EQual$ R0 = 1 si $R1 == $R2, 0 sinon (pseudo-instruction)
Instructions de saut
Branchements Conditionnels :
beq $R0, $R1, label # saute au label si $R0 == $R1
bne $R0, $R1, label # saute au label si $R0 != $R1
bgez $R1, label # saute au label si $R1 >= $zero
bgtz $R1, label # saute au label si $R1 > $zero
blez $R1, label # saute au label si $R1 <= $zero
bltz $R1, label # saute au label si $R1 < $zero
j label # Jump saute au label
jal label # Jump And Link saute au label (de fonction) et
# copie le compteur d'instruction dans le registre $ra
jr $R0 # Jump Register saute à l'adresse contenu dans le registre $R0
jr $ra # saute à l'adresse de retour de fonction positionnée par "jal"
Appels Systèmes
Les appels systèmes de MIPS réalisent les interactions entre l'exécutable et le système d'exploitation : Entrées/Sorties, allocation mémoires, exit, heure,... MIDI,...
L'appel est réalisé par l'instruction syscall, le type d'appel est préalablement stocké dans le registre $v0, et les éventuels arguments
dans les registres $a0-a4 (à sauvegarder si besoin !). En sortie, le résultat éventuel de l'appel utilisera les registres $v0 et $v1.
Les principaux appels systèmes sont :
Exemples :
Service | code dans $v0 | Arguments en entrée | Résultat en sortie |
---|---|---|---|
Print Integer | 1 | $a0=entier à imprimer | |
Print String | 4 |
$a0=adresse de la chaîne à imprimer, la chaîne termine par \0 |
|
Read Integer | 5 | $v0=entier lu | |
Read String (fgets) | 8 |
$a0=adresse du buffer d'entrée $a1=taille du buffer |
chaîne à l'adresse $a0, la chaîne terminée par \0 |
Sbrk (malloc) Allocate heap memory |
9 | $a0=nombre d'octets à allouer |
$v0=adresse mémoire allouée et non initialisée |
Exit | 10 | ||
Print Character | 11 | $a0=caractère à imprimer | |
Read Character | 12 | $v0=caractère lu | |
Exit with status | 17 | $a0=valeur de retour de l'exécution | exit status du processus |
# Imprimer un entier contenu dans $t0
li $v0, 1 # syscall code = 1 (print integer)
move $a0, $t0 # set argument $a0: $a0 = $t0
syscall # appel l'OS pour imprimer
# Lire un entier au clavier : readline() et parseint()
li $v0, 5 # syscall code = 5 (read integer)
syscall # appel l'OS pour lire
move $v0, $t0 # entier lu dans v0, est sauvé dans $t0
# Imprimer une chaine avec fin de ligne
li $v0, 4 # syscall code = 4 (print string)
la $a0, str1 # $a0 = adresse de la chaine
syscall # imprime str1
li $v0, 11 # syscall code = 11 (print char)
li $a0, 10 # $a0 = `\n`
syscall # imprime la fin de ligne
.data
str1 .asciiz "Print this" # la chaine
# Exit(n)
li $v0, 17 # syscall code = 17 (exit with status)
li $a0, 666 # $a0=666;
syscall # exit(666);
Appels Systèmes (Bonus)
Deux appels systèmes spécifiques à Mars :
Service | code dans $v0 | Arguments en entrée | Résultat en sortie |
---|---|---|---|
MIDI out Asynchronous | 31 |
$a0=note 0-127 $a1=durée (ms) $a2=instrument 0-127 $a3=volume 0-127 |
Joue la note et retour immédiat |
MIDI out Synchronous | 33 |
$a0=note 0-127 $a1=durée (ms) $a2=instrument 0-127 $a3=volume 0-127 |
Joue la note et retour après la durée |
- Note de 0 à 127. Note 57 = 440Hz = la3 (A4). Douze notes par octave.
- Instruments de 0 à 127, 16 familles de 8 instruments :
- Piano(0-7), Percussions chromatiques(8-15), Orgues(16-23), Guitares(24-31)
- Basses(32-39), Cordes(40-47), Orchestre(48-55), Cuivres(56-63)
- Anches(64-71), Flutes(72-79), Synthétiseur solo(80-87), Nappes de synthétiseur(88-95)
- Effets de synthétiseur(96-103), Instruments ethniques(104-111), Percussions(112-119), Effets sonores (120-127)
- cf. Liste détaillée
CSC4251-52 (CSC 4536), TELECOM SudParis, P. Hennequin,Last modified: Juillet 2022