Vous êtes sur la page 1sur 140

Le MIPS

Microprocesseur RISC
débuts en 1985, gamme de processeurs :
R2000 / R3000 / R4000 / R8000 / R10000
Équipe de nombreux types de machine
Playstation, Nintendo 64, stations Silicon Graphics (dédiées à la 3D),
société MIPS filiale de SGI
machine à chargement/rangement
machine à registres généraux
Application de l'approche RISC
Performances d'un processeur sur l'exécution de plusieurs
instructions :
approche CISC : plusieurs cycles pour une instruction.

Instruction simple

Instruction complexe
Application de l'approche RISC
Utilisation intéressante du temps : instructions simples effectuées
en peu de cycles, instructions complexes en plus de cycles (le
nombre de cycles nécessaires).
Mais complique énormément une gestion de pipelines éventuels,
car les instructions n'ont pas le même format ni la même
séquence d'exécution.
Approche RISC : toute instruction s'exécute de manière
similaire, en suivant un chemin de données bien défini : Fetch,
UAL , Mémoire, Résultat.
cycle 1 cycle 2 cycle 3 cycle 4
Fetch Opération Accès Ecriture
instruction UAL Mémoire Résultat
Exécution en pipeline
Amélioration potentielle du nombre d'instructions par cycle en
utilisant un pipeline :
chaque cycle de l'instruction utilise une partie du processeur. A
chaque cycle, on choisit de commencer le traitement d'une
nouvelle instruction :

F UAL M R

F UAL M R

F UAL M R
Flot
F UAL M R
d'instructions
cycle
Exécution en pipeline
Suppose que le pipeline contienne toujours des instructions utiles
et qu'il n'y ait pas besoin de rajouter des cycles vides pour
synchronisation.
Exemple d'un pipeline CISC, utilisé sur 6 instructions

4 cycles

8 cycles En gris : utilisation


8 cycles exclusive d'une ressource,
ici l'UAL.
12 cycles
4 cycles

8 cycles
Exécution en pipeline
Sans pipeline : en tout 44 cycles pour 6 instructions : 7,33
cycles/instruction. Avec pipeline : cycles de délai lorsqu'une
ressource n'est pas utilisable, matérialisé par :

29 cycles
Exécution en pipeline
Avec pipeline, même s'il y a du remplissage : 29 cycles, soit 4,83
cycles/instruction .
Gain contrebalancé par :
gestion du pipeline pour tenir compte des cycles vides,
des instructions de longueur variable : compliqué
évènements qui perturbent le déroulement séquentiel du
programme : sauts conditionnels, interruptions
compilateur a du mal à ordonner les instructions pour
tirer le meilleur parti du pipeline.
Machine à chargement/rangement
Efficacité du découpage si les 4 phases F, UAL, M, R
consomment une quantité de temps à peu près comparable.
Or les accès mémoire sont longs :
calcul de l'adresse, accéder aux opérandes en mémoire,
les traiter, les ranger.
Choix d'une architecture à chargement/rangement (instructions
LOAD et STORE). Seules ces instructions permettent un accès à
la mémoire.
Gain en terme de bande passante (réduction des accès)
Jeu d'instructions plus simple
Optimisation de l'utilisation des registres par compilateur
Machine à chargement/rangement
Chargement retardé (delayed LOAD).
Lors d'une opération de chargement, la donnée à charger dans un
registre n'est pas disponible immédiatement pour l'instruction
suivante si l'on utilise un pipeline :

Donnée chargée disponible


comme opérande

LOAD F UAL M R

F UAL M R

F UAL M R

F UAL M R
Machine à chargement/rangement
Chargement retardé (delayed LOAD).
Solution : insérer des cycles d'attente lors de l'exécution de
l'instruction 2, entre les phases F et UAL :
lors de la phase UAL, l'instruction 2 peut avoir besoin de la
donnée de l'instruction 1.
Technique simple : faire apparaître cette dépendance au
compilateur, toute instruction de chargement à un délai de
latence de 1 cycle, qui correspond au début de traitement d'une
nouvelle instruction.
L'instruction suivant une instruction de chargement est dite dans
la fenêtre ou intervalle de retard de chargement.(Load delay
slot)
Machine à chargement/rangement
Si cela apparaît au niveau du compilateur : pas de dépendance
dans le pipeline entre l'instruction de chargement et celle qui est
dans son load delay slot.
Il existe une instruction qui n'a aucune dépendance avec les
autres : NOP !
Pas besoin de faire un contrôle matériel du pipeline. Mais
utilisation du NOP augmente la taille du code, augmente le délai
d'exécution sans faire de tâche utile !
Autre solution : utiliser une instruction utile dans le load delay
slot, optimisation faite par un compilateur.
Machine à chargement/rangement
Exemple : code pour C=A+B; F=D
Load R1,A
Load R2,B dans le load delay slot de Load R1,A : pas de dépendance
Add R3,R1,R2 dans le load delay slot de Load R2,B : dépendance à R2 : attente
Load R4,D
autre version :
Load R1,A
Load R2,B dans le load delay slot de Load R1,A : pas de dépendance
Load R4,D dans le load delay slot de Load R2,B : pas de dépendance
Add R3,R1,R2 dans le load delay slot de Load R4,D : pas de dépendance
Branchements retardés
Les instructions de branchement/sauts introduisent aussi des délais
dans le pipeline car il faut calculer l'adresse de destination avant de
procéder à la phase F (Fetch) de l'instruction suivante.
Lorsqu'un accès à la mémoire d'instructions nécessite un cycle
entier, et que l'instruction de saut correspondante indique l'adresse
de destination, il est impossible de faire tout de suite la phase F de
l'instruction suivante sans insérer un délai d'un cycle
SAUT F UAL M R

Branch delay slot

F UAL M R

adresse trouvée disponible F UAL M R


pour phase F suivante
Branchements retardés
Les instructions de branchement conditionnels peuvent causer des
retards supplémentaires : condition à évaluer.
Technique choisie : comme pour le chargement retardé, on exécute,
dans le branch delay slot, une ou plusieurs instructions avant
d'effectuer le saut.
Si cela n'est pas possible, insertion de NOPs, mais un compilateur
est capable de réordonner des instructions pour remplir
efficacement le branch delay slot.
On cherche à mettre dans ce branche delay slot une instruction
écrite avant dans le programme assembleur, et qui ne présente pas
de dépendance avec le saut.
Branchements retardés
Illustration : programme avec saut
première version : avec branchements retardés explicitement

A: move s0,a0 transfert de registre à registre


move s1,a1 idem
addiu s0,s0,1 addition s0 s0+1
beq s0,$0,SUITE saut à suite si S0=0
nop branch delay slot 'rempli' avec nop
B: move a0,s0
move a1,s1
SUITE: store A,t3 autres instructions
syscall
jal FIN
Branchements retardés
Illustration : programme avec saut
deuxième version : branch delay slot 'intelligemment rempli'

A: move s0,a0 transfert de registre à registre

addiu s0,s0,1 addition s0 s0+1


beq s0,$0,SUITE saut à suite si S0=0
move s1,a1 branch delay slot avec instruction
indépendante du saut et des valeurs testées par le saut.
B: move a0,s0
move a1,s1
SUITE: store A,t3 autres instructions
syscall
jal FIN
Branchements retardés
Souvent, une instruction qui se situe avant un branchement peut être
exécuté après le début de traitement du branchement sans affecter la
logique du programme ou le branchement lui-même.
Temps d'accès aux instructions
Le temps nécessaire à accéder à une instruction dépend très
largement du système physique de mémoire utilisé et devient le
facteur limitant dans la conception des processeurs à cause de la
vitesse à laquelle il est nécessaire de fournir des instructions au
CPU.
Utilisation de caches mémoires pour accélérer ces traitements :
séparation en
cache pour les instructions : joue le rôle de mémoire
d'instruction (schéma de contrôle du MIPS de l'année dernière)
cache pour les données : joue le rôle de mémoire de données
Temps d'accès aux instructions

CPU
données @

Cache
d'instructions

Cache de
données

données @
Mémoire principale
Le système de gestion de mémoire
Système de gestion de la mémoire de type MMU (Memory
Management Unit) intégré au coprocesseur CP0 (contrôle, pas
arithmétique)
selon le mode utilisé (utilisateur ou noyau), adresses référencées
sont ou non traduites en adresses physiques à l'aide d'un TLB
(buffer de traduction d'adresse virtuelle vers physique).
Invisible la plupart du temps pour le programmeur, mais une
mauvaise utilisation d'adresses (mauvaises valeurs) peut provoquer
des erreurs du TLB.
Ce ne sont pas des erreurs de bus ou d'alignement !
Le MIPS
Respecte entièrement la philosophie RISC
registres : 32 registres de 32 bits
instructions : toutes codées sur 32 bits, découpage cohérent pour un
traitement rapide
registre PC et adresses codées sur 32 bits
la mémoire est adressable par octet, transferts de 8, 16 ou 32 bits
possibles
mémoire maximale adressable : 232 octets = 22.210.210.210 octets = 4
Go
Le MIPS
Constitué de deux parties distinctes
le CPU : registres généraux
registres HI et LO
UAL
PC
le CP0 : processeur de contrôle système intégrant
TLB pour la gestion de mémoire virtuelle
registres de gestion mémoire
registres de traitement des exceptions
registres d'état
Le MIPS
Registres généraux :
nommage particulier :
$0 à $31
$0 : vaut toujours 0 (câblage)
$31 : utilisé par certaines instructions comme sauvegarde
ne pas utiliser $31 comme un registre général
autre convention de nommage, plus pertinente mais contredit le
caractère général des registres
Format des instructions MIPS
Toutes les instruction du R2000 sont des instructions 32 bit, et il y a
3 formats d'instruction différents, cela pour simplifier le décodage et
utiliser un séquenceur câblé.
Les opérations plus complexes peuvent être simulées grâce à une
suite d'instructions simples, on les trouvera sous la forme de pseudo-
instructions.
Exemple : un transfert de registre à registre n'est pas implémenté,
mais la pseudo-instruction move réalise cette opération.
Lorsque l'on écrit move Rsource,Rdest, en fait on réalise
l'instruction MIPS : add Rdest,Rsource,$0
Format des instructions MIPS
On distingue 3 formats d'instructions :
• les instructions de type I : opérande de type valeur
immédiate;
• les instructions de type R, les opérandes sont des registres
• les instructions de type J, (Jump, saut), l'opérande est
l'adresse choisie
Ces formats ne correspondent pas aux différentes catégories
d'instructions
Catégories des instructions MIPS
répartition en 6 catégories :
• Load/Store
• calculatoires : arithmétiques, logiques, décalages/rotations
• sauts et branchements
• instructions du coprocesseur arithmétique (R2010 FPA)
• instructions du coprocesseur de contrôle CP0 (gestion de
la mémoire virtuelle, gestion des interruptions et exceptions)
• instructions spéciales : appels système, points d'arrêt
Retour sur les formats d'instruction
Type I
op rs rt immediate
Type J
op target
Type R
op rs rt rd shamt funct
Retour sur les formats d'instruction
Avec : op : opcode sur 6 bits
rs : n° de registre source sur 5 bits
rt : n° de registre de transit sur 5 bits
rd : n° de registre de destination sur 5 bits
immediate : valeur immédiate 16 bits
target : adresse cible sur 26 bits
shamt : valeur de décalage sur 5 bits (pour les instructions de
décalage, rotations)
funct : champ de fonction sur 6 bits
Le langage Assembleur
En fait, leS langageS d'assemblage. A chaque architecture son
langage (dommage).
Produit le code réputé le plus efficace en rapidité d'exécution
proche de la machine (quoique…)
donc hélas éloigné du programmeur
productivité plus faible en assembleur qu'en C
code produit par les compilateurs de plus en plus sophistiqués.
Utilité pour certaines applications (temps réel)
Le langage Assembleur
Au départ : traduction quasi-directe en langage machine
(traduction 'à la main' en hexadécimal, en binaire, en trous ! avec des
tables de conversion)
des programmes :

0x6461425b 0x64646120 0x73736572 0101010000111111010101


0x206e6920 0x61746164 0x6174732f 0100100101010100100111
0x72206b63 0x5d646165 0x20200020 1010100011101010010101
0x7272455b 0x6920726f 0x7973206e 0001011110101000001111
0x6c616373 0x00205d6c 0x425b2020 1110000111110000010100
0x6b616572 0x6e696f70 0x00205d74
0000111111000000000000
0x525b2020 0x72657365 0x20646576
001101000011111000001
Le langage Assembleur
Aujourd'hui, langage assembleur traduit en langage machine de
manière plus complexe
une instruction assembleur : plusieurs instructions du microprocesseur
ex du MIPS :

0x3c011001 lui $1, 4097 [mess1] la $a0,mess1


0x34240016 ori $4, $1, 22 [mess1]

Instructions du Instructions en
Code + opérandes
microprocesseur assembleur
en hexadécimal
Le langage Assembleur
Aujourd'hui, langage assembleur traduit en langage machine de
manière plus complexe :
existence de pseudo-instructions (qui ne sont pas comprises par tous
les assembleurs MIPS).
Piège pour l'évaluation de performances, en nombre de
cycles/instructions :
cycles / instructions en langage machine, et non cycles / instructions
en assembleur !
En général, pseudo-instructions très bien traduites : utilisation
naturelle, pas de précaution à prendre pour leur emploi.
Le langage Assembleur
Pas de typage fort des données : emploi sans contrôle des octets ou
groupes de 16 ou 32 bits selon les besoins
Pas de protection du code vis-à-vis des données :
localisation mémoire différente (explicite)
mais accès indifférent
code auto-modifiable
un programme peut écrire dans la zone mémoire où sont stockées ses
instructions !
(très simple en MIPS !)
Le langage Assembleur
Connaissance du "modèle de la mémoire" du processeur ou de
l'assembleur.
Attention aux différences possibles entre :
• Simulation d'un processeur : interprétation du code binaire ou
des instructions assembleur
• Exécution du programme en code binaire sur une cible
(processeur + carte)

Simulateur : tentation de faire plus simple que la réalité ! C'est le cas


avec le MIPS !
Le langage Assembleur
Modèle de mémoire simplifié : segment de donnée (DATA) /
segment de code (TEXT)
0x7fffffff
STACK

adresses
DYNAMIC (heap)
DATA
STATIC
0x10000000

TEXT
0x00400000
Réservé
Modèle des registres
Noms associé à un rôle que jouent les registres lors de programmes
comportant plusieurs langages source (C et ASM le plus souvent)

noms normaux noms conventionnels remarque


$0 $0 vaut 0
$1 $at ne pas utiliser
$2 - $3 $v0 - $v1 valeur de retour de procédures
$4 … $7 $a0 … $a3 4 premiers arguments de fonctions
$8 .. $15 et $24,$25 $t0 … $t9 valeurs temporaires
$16-$23 $s0 … $s9 ne doivent pas être modifiés par fonctions
$26 - $27 $k0 - $k1 ne pas utiliser
$28 $gp pointeur global : accès valeurs statiques
$29 $sp pointeur de pile
$30 $fp pointeur de haut de zone de pile (frame)
$31 $ra ne pas utiliser
Modèles des registres
En pratique : utiliser, pour des programmes simples :
$0, $v0, $v1, $a0 … $a3, $t0 … $t9 (16 registres)
Modes d'utilisation :
mode utilisateur (ou U / User)
mode noyau (ou K / Kernel)
protection de ressources, accès au mode noyau facile cependant !
Modèle mémoire différent selon le mode
U : 2 Go
K : jusqu'à 4 Go
Forme d'un programme
Un programme en assembleur est constitué d'instructions et de
directives

• Instruction : ordre donné au processeur pour effectuer une


action lors de l'exécution du programme
• Directive : ordre donné à l'assembleur pour préparer la
traduction du code source en langage machine
Forme d'un programme
Exemples d'instructions :
addu $t0,$t1,$t6 effectue $t0  $t1+$t6 (registres)
j $ra effectue : saut à l'adresse donnée par $ra
bgezal $s4,destination compliquée !

exemple de directives :
.text indique que les lignes suivantes seront rangées dans le
segment TEXT
.data indique que les lignes suivantes seront rangées dans le
segment DATA
.byte 6 stocke la valeur 6 dans un octet de la mémoire
Écrire un programme
Exemple avec un programme affichant "hello world !"

.data
hello: .asciiz "hello world\n"
directives
.text
.globl main

main:
li $v0,4
la $a0,hello instructions
syscall
j $ra

labels
Écrire un programme
Les lignes indispensables : programme minimal

.data (directive) segment de données

.text (directive) segment de programme

.globl main (directive) exportation du label 'main'

main: (label) repère de début de programme


j $ra (instruction) retour au système (fin du prog)

Données ajoutées dans le segment de données, instructions dans le


segment de texte.
Écrire un programme
Choix de la zone mémoire à utiliser avec les directives .data et .text:
possibilité de préciser l'adresse de départ de la zone en l'ajoutant à la
suite des directives .data ou .text
syntaxe : .data adresse
.text adresse
équivalent de ORG en 68000, il est possible d'utiliser plusieurs
directives .data ou plusieurs directives .text dans un même
programme source en assembleur.
Par défaut : .data :adresse 0x10010000
.text :adresse 0x00400000
Quelques directives
Mettre des valeurs dans la mémoire : principalement insérer des
constantes, ou initialiser des "variables".
À utiliser dans le segment de données .data ! Ne sont pas des
instructions exécutables la plupart du temps.
Principe : l'octet (les octets) situé(s) à une certaine adresse mémoire a
la valeur précisée.
Syntaxe
.byte b1[,b2,…,bn] : stocke les valeurs 8 bits listées dans des octets consécutifs
.half h1[,h2,…,hn] : stocke les valeurs 16 bits listées dans des octets consécutifs
.word w1[,w2,…,wn] : stocke les valeurs 32 bits listées dans des mots consécutifs
Quelques directives
.float f1[,f2,…,fn] : stocke les valeurs float (32 bits) listées dans des mots
consécutifs
.double d1[,d2,…,dn] : stocke les valeurs float (32 bits) listées dans des mots
consécutifs

.ascii[z] "chaîne" : stocke les codes ascii des caractères de la chaîne en


mémoire à des adresses consécutives. Stocke un 0 à la fin si asciiz est employé.

Notations : utilisation de constantes numériques

•notation en base 10 par défaut, avec signe possible

•notation en base 16 possible en faisant précéder le nombre de '0x',


nombre non signés en hexadécimal !
Directives de remplissage de RAM
Exemples
.data
.byte 0xff,
.byte 0x3e, 12,-5

donnera en mémoire, avec la présentation utilisée par le simulateur


(attention, les PC sont des machines petit-boutistes !)
[0x10010000] 0xfb0c3eff 0x00000000 0x00000000 0x00000000

adresse Mots de 32 bits


Directives de remplissage de RAM
Exemples
.data
.half 0xa5a5,0xb6b7

donnera en mémoire, avec la présentation utilisée par le simulateur


(attention, les PC sont des machines petit-boutistes !)
[0x10010000] 0xb6b7a5a5 0x00000000 0x00000000 0x00000000

adresse Mots de 32 bits


Directives de remplissage de RAM
Ces directives peuvent aussi préciser le nombre de fois où ces valeurs
doivent être écrites en RAM.
Syntaxe :
.format val1:nb1
écrit nb1 fois de suite la valeur val1 en RAM
Dans ce cas, on ne précisera qu'une seule valeur par ligne. (erreur du
simulateur sinon)
Directives de remplissage de RAM
exemple :
.data
.half 0xa5a5:3
.half 0xb6b7
.byte 12:3
.byte 0xfb:2

donnera en mémoire :
[0x10010000] 0xa5a5a5a5 0xb6b7a5a5 0xfb0c0c0c 0x000000fb
Les constantes à virgule
Écriture des nombres en virgule flottante, formats .float et .double : on
utilise une notation scientifique
exemples :
.data
.float 1.1
.float -1.1e-24

donnera en mémoire
[0x10010000] 0x3f8ccccd 0x97aa377d

ne pas utiliser de notation en hexadécimal pour les constantes à virgule


: pour ces notations, utiliser .word
avec les constantes à virgule, on ne peut pas utiliser les répétitions de
placement en mémoire.
Réservation d'octets
La directive .space permet de "réserver" un certain nombre d'octets,
comme le permettait la directive DS.B du 68000.
L'espace 'réservé' est rempli avec des 0.
Adresse courante, labels, directives
La directive .data indique que les directives suivantes auront une
action dans le segment de données. Avec le simulateur que nous
utiliserons (PCspim), cela correspond à l'adresse 0x10010000.
Un label est un nom associé à l'adresse à laquelle la directive agit.
Exemple
.data travail dans le segment de données. L'adresse
courante est donc 0x10010000 (simulateur)

start_data : label, vaut l'adresse courante, donc 0x10010000

.half 0x520b 0x520b est écrit dans les octets 0x1001000


et 0x10010001 (en petit-boutiste), l'adresse
courante devient 0x10010002

.byte 55 55 est écrit à l'adresse 0x10010002


l'adresse courante devient 0x10010003
Adresse courante, labels, directives
Il est possible d'associer un label à chaque directive, on n'est pas
obligé d'utiliser un label dans un programme en assembleur.
Il n'y a pas de directives ORG ou END avec le langage d'assemblage
du MIPS.
Instructions de base et exemples
Instructions et pseudo-instructions de l'assembleur, et non du langage
machine.
instructions de chargement LOAD :
LB[U] Load Byte [Unsigned]
lb / lbu : chargement de registre par une valeur contenue dans un octet.
Syntaxe :
lb[u] destination,adresse
l'octet contenu à l'adresse donnée par la deuxième opérande est étendu
sur 32 bits puis rangé dans le registre de destination.
Instructions de base et exemples
Extension de la valeur 8 bits :
si l'instruction est lb :
extension de signe de 8 à 32 bits : les 24 bits restants du
registre prennent la valeur du bit de signe de l'octet lu en mémoire
si l'instruction est lbu :
extension par zéro de 8 à 32 bits : les 24 bits restants du
registre prennent la valeur 0.
Instructions de base et exemples
data
valeur1:
.byte 1
.byte -1 # ou .byte 0xff
.text
.globl main
main:
lb $t0,valeur1 # valeur : 1
lb $t1,valeur1+1 # valeur : -1 étendue
lbu $t2,valeur1 # valeur : 1
lbu $t3,valeur1+1 # valeur : -1 non étendue
li $v0,10
syscall

$t0 : 00000001
$t1 : ffffffff
$t2 : 00000001
$t3 : 000000ff
Instructions de base et exemples
Instruction lh/lhu
syntaxe : lh destination, adresse
même principe que l'instruction lb pour les extensions de signe, les
valeurs transférées sont des valeurs 16 bits situées à des adresses
paires.
En cas d'adressage à une adresse impaire pou lh/lhu : erreur
d'adressage (exception), fait planter le programme
instruction lw : load word, chargement de 32 bits suivant le même
principe
syntaxe : lw destination, adresse
l'adresse donnée en opérande doit être multiple de 4.
Instructions de base et exemples
Instructions de chargement avec des valeurs non alignées en
mémoire :
lwl : load word left : charge les x octets de plus haut numéro d'un mot
mémoire dans les 24 bits de poids fort d'un registre.
lwr : load word right : charge les x octets de numéro le plus bas d'un
mot mémoire dans l'octet de poids faible d'un registre.
Utilité : charger des mots non alignés en 2 étapes. La numérotation des
octets lus en mémoire se fait en fonction de la représentation gros ou
petit-boutiste de la machine concernée.
Instructions de base et exemples
Autres affectations qui ne sont pas des chargements (pas de référence
en mémoire)
lui : load upper immediate
syntaxe : LUI destination, immédiate (16 bits)
stocke la valeur immédiate 16 bit dans les 16 bits de poids fort
du registre destination, les 16 bits de poids faible sont mis à 0.
Ex : lui $t0,0x1234
$t0 12 34 00 00
Instructions de base et exemples
Autres affectations qui ne sont pas des chargements (pas de référence
en mémoire)
li : load immediate (pseudo-instruction)

syntaxe : LI destination, immédiate (32 bits)


stocke la valeur immédiate 32 bits dans le registre destination
Ex: li $t4,0xfedcba98

réalisé par : lui : pour charger les 16 bits de poids fort du registre,

ori (OU logique avec une valeur immédiate) pour


charger les 16 bits de poids faible du registre
Instructions de base et exemples
Autres affectations qui ne sont pas des chargements (pas de référence
en mémoire)
la : load address (pseudo-instruction)

syntaxe : LA destination,valeur d'une adresse (32 bits)

même pseudo-instruction que li : le but est d'initialiser un registre avec


une valeur immédiate 32 bits qui est considérée, par le programmeur,
comme une adresse : seule l'utilisation du registre
Le MIPS ne fait aucune différence !

Parallèle avec les instructions MOVE et MOVEA du 68000.


Adresses : absolu ou indirect ?
Instructions de chargement avec adresses non détaillées :
on peut utiliser de l'adressage absolu ou de l'adressage indirect !
A priori, tous les registres (à l'exception de $0, qui est câblé à 0)
peuvent servir de registre d'adresse pour accéder à une cellule
mémoire.
Pour distinguer adressage absolu et adressage indirect : syntaxes
différentes.
Adressage absolu
Pour les instructions de chargement, l'adressage absolu est utilisé
lorsque l'adresse est donnée sous forme d'une constante numérique, au
format décimal ou hexadécimal (précédé de 0x)
exemples :
lh $t0,0x1000
a pour effet de stocker dans le registre $t0 le mot (16 bits) située à
l'adresse 0x1000 en mémoire (puisqu'il s'agit d'une partie de mot
mémoire, attention à la représentation little endian ou big endian)
il est possible d'utiliser un label (de même qu'avec le 68000) pour
réaliser un chargement en adressage absolu.
Adressage absolu
Exemple :
.data
sample:
.half 0xa5b4
.half 0x1234
.text
.globl main
main:
lh $t0,sample #compris comme lh $t0,0x10010000
lh $t1,sample+2 #compris comme lh $t0,0x10010002

li $v0,10
syscall
Adressage indirect avec déplacement
Pour les instructions de chargement, l'adressage indirect est utilisé
lorsque l'adresse est donnée sous forme d'un registre noté entre
parenthèses.
Exemple :
lhu $t0,($t1)
charge dans le registre $t0 le mot 16 bits (sans extension de signe)
situé à l'adresse "pointée" par le registre $t1.
On peut faire précéder cette notation d'une constante numérique (notée
en décimal ou en hexadécimal) indiquant un offset ou déplacement à
appliquer au contenu du registre avant de lire la mémoire.
Il faut toujours que le registre utilisé comme registre d'adresse soit
initialisé !
Adressage indirect avec déplacement
Exemples :
.data
samples:
.word 0x12345678
.word 0xfedbca98
.text
.globl main
main:
la $t1,samples # la : load address : adressage direct et immédiat
lw $t0,($t1) # lw : load word :adressage direct et indirect
lw $t2,4($t1) #lw : adressage direct et indirect avec déplacement

li $v0,10
syscall

le déplacement étant une constante numérique, il peut être remplacé


par un label (qui sera traduit par l'assembleur en une valeur
numérique)
Adressage indirect avec déplacement
Équivalent de la directive EQU du 68000
il est possible de définir des symboles non comme des adresses
(labels), mais comme des constantes numériques, comme la directive
EQU du 68000 ou le #define du langage C.
syntaxe :
symbole = expression
ou
symbole = registre
lorsque l'on veut renommer un registre pour des facilités de
programmation.
Utile pour les décalages utilisés avec l'adressage indirect du MIPS.
Adressage indirect avec déplacement
Reprise de l'exemple précédent :

offset = 4

.data
samples:
.word 0x12345678
.word 0xfedbca98
.text
.globl main
main:
la $a1,samples # la : load address : adressage
direct et immédiat
lw $t0,($a1) # lw : load word :adressage
direct et indirect
lw $t2,offset($a1)

li $v0,10
syscall
Instructions de rangement
Permettent de transférer le contenu de registres vers des octets, mots
16 bits ou mots 32 bits.
Instructions de base :
sb (store byte), sh, sw
syntaxe :
SB registre,adresse : concerne les 8 bits de poids faible du registre
SH registre, adresse : concerne les 16 bits de poids faible du registre
SW registre, adresse : concerne tout le registre
attention : la représentation little ou big endian ne concerne pas les
registres, mais seulement les mots mémoire !
Instructions de rangement
L'alignement des adresses doit être respecté :
sb à toutes les adresses
sh seulement à des adresses paires
sw seulement à des adresse multiples de 4
dans le cas contraire : erreur d'adressage, le programme plante.
Il est possible, comme pour les chargements, d'utiliser l'adressage
indirect avec déplacement (même syntaxe) pour ranger une valeur en
mémoire.
Instructions de rangement
Exemple : ranger en mémoire, à l'adresse 0x2000, les 16 bits de poids
faible du registre $t6.
On utilise les registres $t2 et $s3 comme registres d'adresse (on peut
utiliser n'importe lequel, à l'exception de $0), pour montrer deux
manières différentes d'arriver au même résultat

.text
.globl main
main: li $t6,0xfedcba98 # initialisation de $t6
la $s3,0x2000 # adressage direct et immédiat
la $t2,0x1000 # adressage direct et immédiat
sh $t6,($s3) # adressage direct et indirect
sh $t6,0x1000($t2) # adressage direct et indirect

j $ra
Instructions de rangement
Il n'est par contre pas possible de ranger en mémoire des valeurs
immédiates avec une instruction. Pour cela, on utilisera 2 instructions :
chargement d'un registre avec la valeur immédiate concernée
rangement du contenu du registre en mémoire.
Il est possible, par contre de stocker 64 bits situés dans deux registres
consécutifs grâce à la pseudo-instruction Store Doubleword.
Syntaxe : SD registre_n°_n,adresse
stocke les valeurs des registres numéro n et n+1 dans les cellules
mémoires situées à l'adresse mentionnée.
Autres affectations
Réalisation de transferts de registre à registre.
Syntaxe :
move destination,source (pseudo-instruction)
transfère sur 32 bits le contenu du registre source vers le registre
destination.
Réalisée en faisant une addition entre le registre source et le registre
$0, addition dont le résultat est rangé dans le registre destination.

Autres instructions concernent les résultats d'opérations arithmétiques


et logiques.
Instructions AL
En nombre entiers : utilisation de l'UAL du MIPS pour les opérations
les plus courantes.
Rappel : opérations systématiquement réalisées entre registres, jamais
d'adressage absolu ou indirect !
Prise de valeur absolue (au sens mathématique) :
syntaxe : abs destination,source (pseudo-instruction)
calcule la valeur absolue du registre source (à partir de la
représentation signée en complément à 2) et la range dans le registre
destination.
Instructions AL
Additions : toujours entre trois registres, 2 registres source et un
registre destination
addition avec ou sans prise en compte de l'overflow (équivalent du
flag V de dépassement arithmétique)
syntaxe : add destination,source1,source2
réalise : destination  source1+source2
syntaxe : addu destination,source1,source2
réalise : destination  source1+source2, overflow ignoré
l'oVerflow arithmétique, lorsqu'il apparaît, se traduit par la
manifestation d'une exception (pas simplement le flag V) : traitement
spécial !
Instructions AL
Exemples :
.text
.globl main
main :
li $t0,0x40000000
li $t1,0x40000000
addu $t2,$t1,t0
#affichage 1
add $t2,$t1,$t0
#affichage 2

affichage 1 : 1073741824+1073741824 = -2147483648


affichage 2 : Exception 12 [Arithmetic overflow] occurred and ignored
1073741824+1073741824 = -2147483648

add provoque une exception : dans le simulateur, affichage d'un


message (par le noyau) et poursuite du programme.
Instructions AL
Addition avec des valeurs immédiates (16 bits).
Rappel : instruction codée sur 32 bits, donc pas de possibilité d'avoir
des constantes (valeurs immédiates) sur 32 bits !
La valeur immédiate, codée sur 16 bits, est étendue sur 32 bits avec
son signe, puis ajoutée à un registre source. Le résultat est stocké dans
un registre destination. Un dépassement arithmétique peut se produire.
Syntaxe :
addi destination,source,immédiate
ou
addiu destination,source,immédiate
avec addiu, un éventuel dépassement est ignoré.
Instructions AL
Soustraction : possible seulement avec des registres. Il faut utiliser
l'addition (addi ou addiu) pour soustraire des valeurs immédiates.
Il en existe aussi deux versions, avec prise en compte ou non du
dépassement.
Syntaxe :
sub destination,source1,source2
ou
subu destination,source,source2
Instructions AL
Opérateurs logiques AND, OR, XOR, NOR
avec valeur immédiate : syntaxe
ANDI (ORI , XORI) destination, source, immédiate
effet : étend la valeur immédiate 16 bits avec des 0 sur 32 bits, puis
effectue :
destination  source AND (OR, XOR) immédiate 32 bits
avec registres : syntaxe
AND (OR, XOR, NOR) destination, source1, source2
effectue :
destination  source1 AND (OR, XOR, NOR) source2
Instructions AL
Exemples :
récupérer l'octet de poids faible du registre $t3 et le ranger dans le
registre $t4
andi $t4,$t3,0x00ff

extension de 0x00ff en 0x000000ff, puis application de l'opérateur


logique : $t4  $t3 AND 0x000000ff
réalisation du non logique : on utilise un XOR.

li $t0,-1 # réalise : $t0  0xffffffff


li $t3,0xfedcba98
xor $t4,$t3,$t0
Instructions AL
Les décalages de bits : Shitf Left/Right Logical/Arithmetic
décalages logiques ou arithmétiques de registres.
Décalage arithmétique effectué vers la droite seulement ! Car cette
opération a un sens arithmétiquement : division entière par 2.
Nombre de décalages à faire : précisé par une valeur immédiate shamt
pour SHift AMounT codée sur 5 bits (car 25 = 32, valeur maximale du
décalage) ou par un registre (les 5 bits de poids faible du registre).
Lors des décalages logiques, les bits 'entrants' sont des 0.
Instructions AL
Les décalages de bits : Shitf Left/Right Logical/Arithmetic
décalages logiques avec valeur immédiate 5 bits
SLL (SRL) destination,source,shamt
effectue : destination  source décalé de shamt bits

décalage logique avec registre, le décalage est Variable


SLLV (SRLV) destination,source,décalage
effectue : destination  source décalé de décalage bits
Instructions AL
Exemples de décalages logiques :
.data
.text
.globl main

main: li $t0,-1
sll $t1,$t0,12
srl $t2,$t1,1
li $t4,6
srlv $t1,$t0,$t4

fin: li $v0,10
syscall
Instructions AL
Décalages arithmétiques :
lors d'un décalage arithmétique à droite, les bits entrants sont égaux au
bit de signe du registre à décaler
syntaxe :
SRA destination, source, shamt
ou
SRAV destination, source, décalage

le traitement du bit entrant permet d'effectuer une division entière par


2 quel que soit le signe de la valeur traitée par décalage !
Instructions AL
Multiplications, divisions : se font toujours entre registres, donc entre
quantités 32 bits, peuvent donner des résultats sur 64 bits !
Utilisation de deux registres spéciaux : HI et LO, de 32 bits chacun.
Avec la multiplication : HI et LO contiennent le résultat sur 64 bits
(HI : 32 bits de poids fort; LO : 32 bits de poids fiable du résultat)
Le division est traitée comme une division entière
HI contient le reste de la division
LO contient le quotient de cette division
possibilité de travailler avec des valeurs considérées comme signées
ou non signées.
Instructions AL
Multiplication :
MULT source1,source2 : multiplication signée
MULTU source1,source2 : multiplication non signée
Division
DIV dividende,diviseur : division signée
DIVU dividende,diviseur : division non signée
les registres HI et LO ne sont pas des registres généraux, on peut
cependant y accéder par des instructions spéciales réservées à cet effet.
Instructions AL
Accès aux registres HI et LO :
possibilité de transferts de et vers HI et LO à partir des registres
généraux du MIPS :
instructions :
Move To :MTHI/ MTLO et Move From : MFHI/MFLO
syntaxe :
MTHI (MTLO) source
effectue HI (LO)  source
MFHI (MFLO) destination
effectue : destination  HI (LO)
Instructions AL
Exemple :
.data
.text
.globl main

main: li $t0,16 # 0x10


li $t1,22 # 0x16
multu $t0,$t1 # HI contient 0, LO contient 0x160
divu $t1,$t0 # HI contient 6, LO contient 1
mflo $t2 # transfert de LO dans le registre $t2

fin: li $v0,10
syscall
Branchements
Possibilités de branchements avec des instructions de type I, R ou J
(J : Jump).
Adresse de destination de saut donnée par :
registre
immédiat 16 bits (décalage)
immédiat 26 bits (instructions de type J)
4 variantes pour cette instruction Jump
Branchements
Saut absolu à une adresse donnée sur 26 bits :
Jump, Jump And Link.
Syntaxe :
J target
JAL target
effet : saut à l'adresse spécifiée en opérande.
Transformation de cette opérande de 26 bits  32 bits : décalage de 2
bits vers la gauche, complétion des 4 bits de poids fort en recopiant
ceux du registre PC.
Branchements
Instruction J : saut simple vers la destination spécifiée : équivalent de
GOTO.
Instruction JAL : Link : lien avec l'instruction de saut. Possibilité de
revenir à l'instruction suivante.
Principe : lorsqu'un saut est effectué, sauvegarde de l'adresse à
laquelle se situe l'instruction suivante. Possibilité de sous-programme
réutilisable à partir de plusieurs appels :
réalisation de fonctions.
Effet de JAL : sauvegarde de l'adresse de retour dans le registre $ra
(qui est le registre $31).
Système simpliste et mal adapté aux fonctions.
Branchements
Saut avec registres :
JR rs : saut à l'adresse contenue dans le registre rs.
JALR rd, rs :saut à l'adresse contenue dans le registre rs. De plus,
l'adresse de l'instruction suivante est placée dans le registre rd pour
offrir une possibilité de retour de fonction/sous-programme.
Exemples : sauts avec Jump
.text
.globl main

main : j suite

fin : li $v0,10
syscall

suite : j fin
Branchements
Exemples (suite) : Jump And Link

.data
message : .asciiz "coucou"
.text
.globl main

main : la $a0,message
jal affiche
la $a0,message+2
jal affiche
li $v0,10
syscall

affiche: li $v0,4
syscall
jr $ra
Branchements
Exemples (suite) : Jump Register (sans link)
.data
message : .asciiz "coucou"
.text
.globl main

main : la $a0,message
la $a1,affiche
jr $a1
fin: li $v0,10
syscall

affiche: li $v0,4
syscall
la $a1,fin
jr $a1
Branchements
Exemples (suite) : Jump And Link register
.data
message : .asciiz "coucou"
.text
.globl main

main : la $a0,message
la $a1,affiche
jalr $a2,$a1
la $a0,message+2
jalr $a2,$a1
fin: li $v0,10
syscall

affiche: li $v0,4
syscall
jr $a2
Branchements
Sauts relatifs et sauts conditionnels : utilisation de l'instruction branch
et de ses variantes.
La valeur du saut effectué correspond toujours à un offset ou décalage
par rapport à la valeur du registre PC indiquant l'adresse de
l'instruction en cours de traitement.
Peu d'instructions de branchement conditionnels (approche RISC), se
basent sur deux tests : égalité et nullité.
6 instructions à retenir (6 codes de conditions pour les branchements)
Branchements
Branch on EQual :
BEQ source1,source2,offset
effectue : offset décalé de deux bits à gauche et étendu sur 32 bits
PC PC+offset si source1=source2
BNE source1,source2,offset
effectue le saut si source1  source2
BLEZ source,offset : effectue le saut si source  0
BGTZ source,offset : effectue le saut si source  0
BLTZ source,offset : effectue le saut si source  0
BGEZ source,offset : effectue le saut si source  0
Branchements
Saut conditionnel et lien :
BGEZAL : BGEZ And Link (registre $ra)
BLTZAL : BLTZ And Link (registre $ra)
Réalisation de saut inconditionnel :
BEQ $0,$0,offset
Branchements
Réalisation de boucles avec les sauts conditionnels.
While condition
{
instructions
}

faite 0 fois ou plus, test de condition effectué avant instructions


se traduit par :

si condition fausse saut après la boucle


instructions
retour au test
instructions après la boucle
Branchements
Réalisation de boucles avec les sauts conditionnels.
i=1;
while (i<1000)
{
printf("%ld\n",i);
i=i+1;
}

faite 0 fois ou plus, test de condition effectué avant instructions


se traduit par :
li $t0,1
li $t1,1000
deb_bouc:
beq $t0,$t1,fin_bouc
# instructions d'affichage
addiu $t0,$t0,1
beq $0,$0,deb_bouc
fin_bouc: instructions après la boucle
Branchements
Idem pour do … while, la traduction ici est plus simple : si la
condition est vraie, on retourne en début de boucle.
Instructions de boucle
si condition vraie retour au début de boucle
instructions après la boucle

La traduction d'une boucle for se fait en notant qu'une boucle for est
strictement équivalente à une boucle while : on reprend donc la
traduction de la première boule while.
Appels SYSCALL
Sur le simulateur : mini système d'exploitation permettant d'appeler
des services 'minimaux' pour les entrées/sorties de base : affichages et
saisies.
Il faut sélectionner le service que le système va rendre et indiquer les
éventuels paramètres à fournir, appeler le système, puis
éventuellement récupérer le résultat.
Principe : le registre $v0 doit contenir le numéro du service. Selon la
valeur de $v0, c'est une saisie ou un affichage qui sera fait; avec un
certain format de données : entier, nombre à virgule, chaîne de
caractères.
Appels SYSCALL
Opération effectuée Service code d’appel arguments retour
Afficher un entier print_int 1 $a0 : entier
Afficher un float print_float 2 $f12 : float
Afficher un double print_double 3 $f12 : double
Afficher une chaîne print_string 4 $a0 : chaîne
Saisir un int read_int 5 Entier dans $v0
Saisir un float read_float 6 Float dans $f0
Saisir un double read_double 7 Double dans $f0
Saisir une chaîne read_string 8 $a0 : buffer, $a1 :longueur
Allocation dynamique sbrk 9 $a0 : nb d’octets Adresse de la zone dans $v0

Fin de programme exit 10


exemples SYSCALL
Exemple : affichage

.data
hello: .asciiz "hello\n" # asciiz ajoute un octet nul en mémoire pour repérer la fin de
chaîne

.text
.globl main

main:
li $v0,4 # code : affiche une chaîne
la $a0,hello # $a0 pointe sur le début de chaîne
syscall # appel système : demande d'affichage de chaîne
jr $ra

autre exemple : fin de programme


li $v0,10 # code : exit
syscall # appel système : demande de fin de programme
Affichage en HEXA
Exemple : affichage en hexa : conversion
.data 0x10010100

donnee: .word 0xfedcba98 # valeur exemple numéro 1


.word 0x0000af04 # valeur exemple numéro 2

str_hex: .ascii "0x" # début de la chaîne a afficher après conversion


.space 8
.byte 0
.align 2
converts: .ascii "0123456789ABCDEF"

.text
.globl main
main: lw $a0,donnee
li $v0,1
syscall
jal convert
la $a0,str_hex
li $v0,4
syscall

li $v0,10
syscall
Affichage en HEXA
convert :
addi $sp,$sp,-4 # déplacer pointeur de pile
sw $ra,($sp) # empilement adresse de retour

lw $t0,donnee+4 # $t0 contient la donnée à convertir


la $a0,str_hex+9 # pointeur sur la zone à remplir avec les caractères hexa
la $a1,converts # pointeur sur la zone contenant les caractères de conversion
li $t2,16 # valeur du diviseur pour convertir en base 16
li $t5,8 # compte le nombre de chiffres restant à traiter
bouc_chif:
beq $t5,$0,fin_conv # est-on en fin de boucle ?
divu $t0,$t2 # division par 16
mfhi $t1 # t1 contient le reste (à afficher)
mflo $t0 # to contient le quotient à traiter
addu $a2,$a1,$t1 # $a2 pointe sur le caractère voulu
lbu $t3,($a2) # récupéré dans $t3
sb $t3,($a0) # et mis dans la chaîne
addu $a0,$a0,-1 # on pointe l'octet précédent de la chaîne
addiu $t5,$t5,-1 # décrémentation $t5
j bouc_chif # retour début de boucle
fin_conv:
lw $ra,($sp) # dépilement adresse de retour
addi $sp, $sp,4 # incrémentation du pointeur de pile
jr $ra # retour à l'appelant
Programmation de la FPU (CP1)
Accès aux registres en virgule flottante
FPU : Floating Point Unit : unité en virgule flottante, appelé aussi
coprocesseur mathématique : possible de lui faire réaliser des
opérations complexes.
Philosophie RISC : ne pas compliquer la tâche du CPU (processeur
'principal'), préférence pour utilisation d'instructions d'interfaçage avec
des coprocesseurs.
Le coprocesseur arithmétique dispose de 32 registres 32 bits pouvant
stocker chacun une valeur au format float.
Peuvent aussi être utilisés comme 16 registres 64 bits pour stocker des
doubles.
Programmation de la FPU
FPU aussi appelée CP1, coprocesseur anonyme dont on manipule les
registres en faisant des transferts grâce à des instructions spécialisées
du MIPS :
chargement / rangement vers les registres de la FPU
transferts entre registres du CPU et registres de la FPU
appels à des calculs de la FPU (add, sub, mul, div)
note sur la présentation : les instructions spécifiques aux coprocesseurs
ne précisent pas le numéro du coprocesseur choisi. Syntaxe de la
forme :
OPCODEz , avec z à remplacer par 1 pour la FPU que nous
utilisons. Il peut y avoir plusieurs FPUs !
Programmation de la FPU
Accès aux registres de la FPU :
32 registres 32 bits stockant chacun une valeur float, ce format est
aussi nommé s pour Single precision
groupés par 2 pour 16 valeurs au format double, ce format est aussi
nommé d.
registres accessibles par leur numéro, précédé d'un $f (parfois…)
exemple : $f0, $f17
Choix entre un registre du CPU et un registre de la FPU fait selon le
contexte : on utilise des instructions spéciales (opcodes dédiés à ces
instructions) : l'assembleur sait quel registre choisir.
Programmation de la FPU
Exemples avec transferts :
instructions :
MTCz source,dest
Move To Coprocessor z (z précise le numéro du coprocesseur)
source : registre du CPU
dest : registre de la FPU
MFCz dest, source
dest : registre du CPU
source : registre de la FPU
Affichage de PI
Application : affichage d'un nombre en virgule flottante, on choisit la
valeur 3.141592654 stockée en mémoire à une certaine adresse.
Regarder la description du service système associé :
Afficher un float print_float 2 $f12 : float

donc il faut ranger la valeur à afficher dans le registre $12 de la FPU.


Exemple avec deux stratégies :
a) charger cette valeur dans un registre du CPU, le transférer dans
le registre $12 de la FPU puis faire l'appel système correspondant
b) charger cette valeur directement dans le registre $12 de la FPU,
puis faire l'appel système correspondant.
Affichage de PI
.data
valeur_pi: .float 3.141592654
.text
.globl main
stratégie a)
main: la $a0,valeur_pi # $a0 pointe sur le mot contenant la valeur
lw $t0,($a0) # récupération de la valeur
mtc1 $t0,$f12 # transfert
li $v0,2
syscall
j $ra

pour la stratégie b), il faut employer de nouvelles instructions :


chargement vers registres de la FPU, rangements depuis registres de la
FPU
Affichage de PI
LWCz registre,adresse
charge le registre de la FPU avec le mot situé à l'adresse fournie.

SWCz registre,adresse
range la valeur du registre de la FPU à l'adresse fournie

stratégie b)

main: la $a0,valeur_pi
lwc1 $f12,($a0)
li $v0,2
syscall
j $ra
Opérations avec la FPU
Utilisation pour des calculs en virgule flottante : instructions
spécifiques, avec :
un opcode
un format (s ou d)
des opérandes

programme exemple : calcul du périmètre d'un cercle dont le rayon est


saisi par l'utilisateur.

Algorithme : afficher texte d'accueil


saisir la valeur du rayon R
faire la calcul de 2*PI*R avec la FPU
afficher le résultat (avec un message)
Opérations avec la FPU
.data mul.s $f12,$f0,$f1
valeur_pi: .float 3.141592654 mul.s $f12,$f12,$f2
const_R: .float 2.0
la $a0,text_res
text_acc: .asciiz "Entrez le rayon:" jal aff_text
text_res: .asciiz "périmètre du cercle :"
li $v0,2
.text syscall
.globl main
main: la $a0,text_acc li $v0,10
jal aff_text syscall
li $v0,6
syscall aff_text:
la $a0,valeur_pi li $v0,4
lwc1 $f1,($a0) syscall
lwc1 $f2,4($a0) j $ra
Autres instructions de la FPU
ABS.fmt dest,source : valeur absolue

ADD.fmt dest,source1,source2 : addition

SUB.fmt dest,source1,source2 : soustraction dest <- source1-


source2
DIV.fmt dest,source1,source2 : division dest <- source1/source2

MOV.fmt dest,source : transfert de registres


Les appels aux sous-programmes
Avec le programme précédent : problème avec les appels aux sous-
programmes.
On doit absolument terminer le programme avec les instructions
li $v0,10
syscall
plutôt qu'en utilisant l'instruction
jr $ra (ou j $ra)

sinon…le programme affiche tout le temps le résultat du calcul, c'est


une boucle sans fin !

explication :
le simulateur utilise un morceau de programme pour interfacer
l'assembleur avec le langage C, et appelle le programme principal
(label main).
Les appels aux sous-programmes
Principe de l'appel de sous-programme :

au moment de l'appel, sauvegarde de l'adresse où reprendre, avec


l'instruction jal (Jump And Link), cette sauvegarde s'effectue dans le
registre $31 (nommé aussi $ra).

Forme d'un programme :

0x00400000 : instructions insérées par le simulateur


0x00400014 : appel au prog : jal main
0x00400018 : nop
0x0040001c : li $v0,10
0x00400020 : syscall
Les appels aux sous-programmes
0x00400000 : instructions insérées par le simulateur
0x00400014 : appel au prog : jal main
sauvegarde de l'adresse 0x00400018 dans le registre $ra

0x00400018 $31 ($ra)


0x00400018 : nop
0x0040001c : li $v0,10
0x00400020 : syscall

0x00400024 : main: instructions du programme


0x0040???? : jal $ra : retour, saut à l'adresse sauvée dans $ra
donc ici, retour à 0x00400018

problème si on effectue dans le programme un autre saut avec


l'instructions JAL : la valeur de $ra est effacée ! Pas de retour possible
pour terminer le programme, il faut donc utiliser le service système
exit. (syscall avec $v0=10)
Les appels aux sous-programmes
Autres solutions prévues : préciser le registre dans lequel on
sauvegarde la valeur de retour lors d'un saut (appel)
instructions JALR

deux syntaxes :
JALR registre : saut à l'adresse donnée par le registre, adresse
de retour sauvegardée dans $ra
JALR registre de lien, registre de saut : saut à l'adresse
donnée par le registre de saut, sauvegarde de l'adresse de retour dans le
registre de lien

problème là encore d'utilisation de registres : ils ne doivent pas être


modifiés, or les registres sont équivalents à des variables globales...
Les appels aux sous-programmes
Il faudrait sauvegarder l'adresse de retour avec chaque saut dans un
espace temporaire.
il faudrait, pour revenir du sous-programme , restaurer l'adresse de
retour.
Idée : utiliser la pile pour sauvegarder cette adresse.
On pourrait connaître cette adresse avant l'appel :
si une instruction d'appel (saut inconditionnel) se situe en
mémoire à une certaine adresse, alors l'adresse de retour est celle de
l'instruction suivante.
Lorsque l'on cherche à exécuter le saut : PC indique l'adresse
de l'instruction de saut, donc il faut sauvegarder PC+4.

Problème : pas d'accès à ce registre PC avec le MIPS !


Les appels aux sous-programmes
Mais on peut tout de même connaître cette adresse de retour une fois
le saut effectué (avec la bonne instruction) dès que le sous-programme
commence à s'exécuter (en fait dès que l'instruction de saut est
terminée).

Si on utilise l'instruction JAL pour effectuer le saut, alors l'adresse de


retour est stockée dans le registre $ra. Il suffit alors d'empiler cette
adresse.

Juste avant de revenir au programme appelant le sous-programme en


cours d'exécution, il suffit de dépiler cette valeur de $ra et de faire un
saut à cette adresse avec l'instruction jr $ra (ou j $ra).
Gestion de la pile
Illustration avec le programme précédent : en début de programme :
empiler $ra (cela permet qu'il soit maintenant écrasé, par l'utilisation
d'un autre JAL, mais qu'on puisse le récupérer), appeler un sous-
programme d'affichage, qui utilise $ra.
A la fin du programme : dépiler $ra (on récupère sa valeur de début de
programme), et rendre proprement la main au système.

Accès à la pile simple, grâce à un registre qui est prévu à cet effet :
c'est le registre $sp ($29 pour l'assembleur).
Pas obligatoire, possibilité d'avoir une autre pile, une pile est juste une
zone de mémoire !

Zone de pile du MIPS gérée en 'descendant' dans la mémoire.


Gestion de la pile
Empiler et dépiler : (par analogie avec le 68000)

lorsqu'on empile une valeur (le plus souvent 32 bits), il faut :


mettre à jour le pointeur de pile $sp : décrémenter sa valeur de
4 octets, afin qu'il ait de la place pour accueillir la valeur sauvegardée
transférer la valeur voulue à l'adresse contenue dans $sp

illustration : pour sauvegarder $ra:


addi $sp,$sp,-4 # $sp  $sp-4
sw $ra,($sp) # store word $ra par adressage indirect

répéter ces deux lignes au début de chaque prog/sous-prog


Gestion de la pile
Empiler et dépiler : (par analogie avec le 68000)

lorsque l'on dépile une valeur :


transférer la valeur depuis la pile vers le registre concerné
incrémenter le pointeur de pile pour 4 octets (si la valeur
restaurée est 32 bits)

illustration avec restauration de $ra :


lw $ra,($sp) # load word $ra par adressage indirect
addi $sp,$sp,4 # $sp  $sp-4

système très souple et adaptable : valeurs autres que 32 bits, autres


registre que $ra pour empiler/dépiler, autre registre que $sp pour
utiliser une zone mémoire comme une pile, etc.
Gestion de la pile
Illustration complète de l'exemple choisi : progression du pointeur de
pile et des valeurs sauvegardées.
Symbolisation de la zone de pile : en partant des adresse hautes (en
bas de la zone), vers les adresse basses : permet d'empiler vers le 'haut'
et dépiler vers le 'bas'. On symbolisera aussi le registre $ra.

Adresse de bas
$sp de zone
Taille en bits(8 ou 16 ou 32)
Exemple
0x00400000 : instructions insérées par le simulateur
0x00400014 : appel au prog : jal main
0x00400018 : nop
0x0040001c : li $v0,10
0x00400020 : syscall
0x00400024 : main : addi $sp,$sp,-4 # ici $ra contient 0x00400018
0x00400028 : sw $ra,($sp)

$sp 0x00400018
0x7fffeffc

32 bits
$ra 0x00400018
Exemple (suite)
0x00400028 :la $a0,text_acc
0x0040002c: jal aff_text
0x00400030: li $v0,6
0x00400034: syscall
# calculs

$sp 0x00400018
0x7fffeffc

32 bits
$ra 0x00400030
Exemple (suite)
0x0040007c: aff_text: li $v0,4
0x00400080: syscall
0x00400084: j $ra

$sp 0x00400018
0x7fffeffc

32 bits
$ra 0x00400030
li $v0,2
Exemple (fin)
syscall
lw $ra,($sp)
addi $sp,$sp,4
j $ra

0x00400018
$sp 0x7fffeffc

32 bits
$ra 0x00400018
Passage de paramètres
Structuration : pas de variables globales.
Utiliser des registres pour transmettre des valeurs : prendre le risque
de modifications dans les sous-programmes !

Procéder en deux étapes :


a) rendre les sous-programmes transparents pour les registres vis-
à-vis du programme ou sous-programme appelant.
Ex : un sous-prog A manipule les registres $t2,$v0,$s4,$a0 et $a2 : pas
visible lors du saut par JAL.
Principe : le sous-programme 'sauvegarde' les valeurs des registres
qu'il manipule après avoir sauvegardé son @ de retour.

Utilisation de la pile suivant le même principe.


Indépendance des registres
Ex du sous-programme A :

A: addi $sp,$sp,-4
sw $ra,($sp) Registres empilés dans l'ordre :
addi $sp,$sp,-4 $ra, $v0, $t2, $s4, $a0, $a2
sw $v0,($sp)
….

# manipulations des registres

lw $v0,($sp)
addi $sp,$sp,4 Registres dépilés dans l'ordre :
lw $ra,($sp) $ra, $v0, $t2, $s4, $a0, $a2
addi $sp,$sp,4
jr $ra
Indépendance des registres
Illustration : soit le sous-programme FOO qui modifie le registre $v0.
.data
donnee : .word 35
.text
.globl main
main: li $v0,3 lw $v0,($sp)
jal FOO addi $sp,$sp,4
li $v0,10 lw $ra,($sp)
syscall addi $sp,$sp,4
jr $ra
FOO : addi $sp,$sp,-4
sw $ra,($sp)
addi $sp,$sp,-4
sw $v0,($sp)
lw $v0,donnee
syscall
Indépendance des registres
Empilement 1

$sp $ra

32 bits

Empilement 2

$sp $v0
$ra

32 bits
Indépendance des registres
Cas général

$sp registre
1 cellule mémoire
par registre sauvegardé

registre
$ra

32 bits
Les paramètres
Etape b) : utiliser des paramètres et non des registres pour
communiquer.
Ne dispense pas de l'étape a) car dans le sous-programme, il peut y
avoir des calculs, des transferts, qui ne se font qu'avec des registres et
non des 'variables' : les variables sont des cellules mémoires et donc
peu manipulables avec le MIPS !

On utilise un espace de stockage temporaire pour stocker les


paramètres (valeurs ou adresse) avant l'appel au sous-programme;
On récupère les paramètres (valeurs ou adresses) dans le sous-
programme à partir de cet espace de stockage intermédiaire qui est
partagé.

Cet espace = la pile.


Les paramètres
Note : l'utilisation d'une pile n'est pas a priori limitée à la seule pile
prévue par le système et pointée par le registre $sp. Méthode
d'empilement/dépilement décrite avec $sp fonctionnne avec n'importe
quel autre registre.

Passage par valeur/par adresse :


on appellera variable un octet, une valeur 16 bits ou un mot mémoire
auquel on aura attribué un label. Un nom de variable, dans ce cas, n'est
qu'un raccourci donnant une adresse.
Par extension, une déclaration de variable sera une directive de
stockage précédée d'un label.
Les paramètres
Exemples :
.data 0x10010180
a: .space 4 # espace de 4 octets nuls
b: .byte 62 # octet initialisé

on cherche à écrire un sous-programme qui stockera dans a la valeur


de b+1, en choisissant que b soit passée par valeur et que a soit passée
par adresse, car elle va être modifiée.
On peut bien sur directement modifier le contenu des cellules
mémoires à l'adresse repérée par le label a !
Avant l'appel du sous-programme : empilement des paramètres.
Les paramètres
Traitement de la valeur b:

pour des questions de régularité, on choisit de ne manipuler que des


valeurs 32 bits lorsqu'il s'agit d'empiler/dépiler.

lb $t0,b Chargement : lecture de mémoire :


accès à la valeur de la 'variable'
addi $sp,$sp,-4
sw $t0,($sp)

la $t0,a initialisation avec une valeur immédiate


accès à l'adresse de la 'variable'
addi $sp,$sp,-4
sw $t0,($sp)

jal sous_prog
État de la pile
sous-prog : addi $sp,$sp,-4
sw $ra,($sp)
# autres instructions

$sp $ra
Adresse de a : 0x10010180
Valeur de b : 62

32 bits
L'allocation dynamique
Appel système avec $v0=9 permet de réserver de la mémoire
dynamiquement dans la zone de tas pour les applications ayant au fur
et à mesure des besoins en mémoire.

Après l'appel système (l'instruction syscall),

Vous aimerez peut-être aussi