SIPRO : Architecture et Instructions du Processeur
SIPRO : Architecture et Instructions du Processeur
Nicolas Bedon
10 octobre 2016
1 Introduction
L’architecture d’un ordinateur est centrée autour d’un composant : le processeur. Le processeur
est un automate sachant réaliser des opérations élémentaires. A l’intérieur d’un processeur se
trouvent de petites mémoires ayant toutes la même taille, de l’ordre de quelques bits. Ces mémoires
sont appelées registres. Un processeur n bits est un processeur dont la taille de chaque registre
est n bits. Un mot machine est un groupe de bits de même taille qu’un registre. Les opérations
élémentaires que sait exécuter le processeur sont souvent réalisées sur des données contenues dans
ces registres. Par exemple, pour faire une addition, le processeur exécute l’instruction suivante :
add ax,bx, où ax et bx sont les noms symboliques de deux registres du processeur. Pour réaliser
cette opération, le processeur regarde les contenus des registres ax et bx, en fait la somme, et
stocke le résultat dans le registre ax.
La taille d’un registre étant petite et les registres étant eux-mêmes en petits nombres, un
processeur peut accéder à un autre composant essentiel de l’ordinateur pour y stocker ou y lire des
données en nombre ou taille importants : la mémoire centrale. La mémoire centrale est une suite
de cellules indicées. La cellule (byte en anglais) est une entité de mémoire de taille constante (par
exemple, 8 bits). L’indice d’une cellule s’appelle son adresse. Certaines instructions permettent
au processeur d’amener le contenu de cellules mémoire dans un registre, et inversement, de copier
le contenu d’un registre dans la mémoire centrale. Ces instructions spécifient (directement ou
indirectement) l’adresse des cellules et le nom du registre concernés. Par exemple, l’instruction
(load byte) loadb ax,bx copie dans ax le contenu de la cellule dont l’adresse est dans bx. Des
instructions permettent également de mettre une constante dans un registre.
Pour le processeur, un programme est une suite d’instructions élémentaires à exécuter. Les
instructions à exécuter sont stockées dans la mémoire centrale, codées avec des valeurs entières
d’octets : les opcodes. On peux imaginer, par exemple, que le codage de l’instruction add ax, bx
est réalisé par la suite de valeurs 20 04 05. La valeur 20 représente le code de l’instruction
d’addition, les valeurs 04 et 05 représentent respectivement les registres ax et bx. Il est très
difficile pour un être humain de programmer le processeur en utilisant directement des opcodes :
il faut en effet se souvenir du code de chaque instruction, et de la signification et du codage des
opérandes éventuels de l’instruction. On utilise plutôt pour programmer un processeur un langage
appelé assembleur. Avec l’assembleur, le programmeur édite un fichier texte et y met la suite des
instructions élémentaires composant son programme, sous la forme add ax,bx, par exemple, à
raison d’une instruction élémentaire par ligne. Le programmeur procède à la traduction de son
fichier texte en suite d’opcodes en utilisant un programme appelé programme d’assemblage. Le
résultat du programme d’assemblage constitue le programme assemblé.
Nous appellerons SIPRO le processeur simplifié décrit dans ce document.
1
2 SIPRO
2.1 Registres
Bien que n’existant pas, SIPRO a une architecture et un comportement très proches de certains
processeurs réels.
SIPRO est un processeur 16 bits. Le codage des entiers positifs ou nuls se fait directement par
leur écriture en base 2, et le codage des entiers négatifs par un complément à deux. Le codage
des caractères se fait en utilisant le code ASCII étendu. L’ordre de stockage des octets d’un mot
machine est gros boutiste : le premier octet contient les bits de poids fort. Pour simplifier nous ne
manipulerons pas de réels.
SIPRO est capable de manipuler une pile de mots machines, dont les valeurs sont stockées dans
la mémoire centrale. Le processeur repère la localisation de cette pile dans la mémoire centrale
à l’aide de deux adresses, celles du fond de pile et du sommet de pile. Ces deux adresses sont
stockées dans deux registres du processeurs, que nous appellerons registres de pile, et dont les
noms symboliques sont bp (pour le fond de pile) et sp (pour le sommet de pile).
SIPRO mémorise l’adresse de l’opcode de la prochaine instruction à exécuter dans un registre
appelé registre d’instruction, de nom symbolique ip.
Certaines opérations élémentaires exécutées par le processeur peuvent générer, en dehors de leur
résultat, des informations qui peuvent être utiles au bon déroulement de la suite du programme.
Par exemple, il est possible, lors de l’exécution d’une addition, que se produise un dépassement de
capacité : le résultat de l’addition est alors erroné. Ce genre d’information est signalé à travers un
registre nommé registre de flags, et dont le nom symbolique est fl. Les bits de ce registre ont une
signification particulière. Le bit de poids le plus faible, nommé e (pour “error”), sert à indiquer
que l’exécution de la dernière instruction a provoqué une erreur. C’est le cas, par exemple, lors
d’une division par 0. Le bit de poids immédiatement supérieur, nommé c (pour “carry”), sert à
propager une retenue sortante de la dernière opération. Ce bit peut être positionné, par exemple,
par l’opération d’addition. Le bit de poids immédiatement supérieur à c, nommé z (pour “zero”),
sert à indiquer que le résultat de la dernière opération exécutée est nul, et est également utilisé
par les opérations de comparaisons. Les autres bits du registre fl ne sont pas utilisés, ils doivent
toujours être positionnés à 0.
ip z c e
fl 15 2 1 0
sp inutilisé
bp
CPU ax
bx
cx
dx
15 0
16 bits
2
2.2 Les accès à la mémoire
2.2.1 Adressage
Pour simplifier, les adresses des cellules mémoire sont numérotées et représentées de la même
façon par le processeur et par la mémoire. L’adresse d’une cellule mémoire tient dans un registre
et est codé par sa représentation en base 2. On est donc dans le cas le plus simple possible. Par
exemple, si le registre bx contient la valeur 2 codée en base 2, alors l’instruction loadb ax,bx
copiera le contenu de la cellule 2 de la mémoire dans le registre ax.
2.3 Instructions
Nous présentons maintenant le jeu d’instructions élémentaires qu’est capable d’exécuter SI-
PRO. Les opcodes, comme toutes les constantes de ce document, sont donnés en hexadécimale.
L’opcode d’une instruction ne dépend pas de ses opérandes. Les registres en argument des instruc-
tions sont identifiés par un code, de taille 8 bits : 00 : ip, 01 : fl, 02 : sp, 03 : bp, 04 : ax, 05 : bx,
06 : cx, 07 : dx.
shiftr
Nom : shiftr Opcode : 10
Syntaxe : shiftr <registre>
Arguments : 1 identifiant de registre : ax, bx, cx ou dx.
Taille totale de l’instruction : 2 cellules (1 pour l’opcode, 1 pour identifier le registre).
Sémantique : décale d’un bit vers la droite tous les bits du registre registre. Le bit perdu est
mis dans le bit c du registre fl. Le bit introduit à gauche est 0.
Exemple : Avant exécution, ax contient (1101000101010011)2 et fl contient (0000000000000111)2 .
Après exécution de shiftr ax, ax contient (0110100010101001)2 et fl contient (0000000000000010)2 :
les bits z et e ont été remis à zéro, et le bit c contient le bit anciennement le plus à droite de ax
qui a été perdu par le décalage.
3
shiftl
Nom : shiftl Opcode : 11
Syntaxe : shiftl <registre>
Arguments : 1 identifiant de registre : ax, bx, cx ou dx.
Taille totale de l’instruction : 2 cellules (1 pour l’opcode, 1 pour identifier le registre).
Sémantique : décale d’un bit vers la gauche tous les bits du registre registre. Le bit perdu
est mis dans le bit c du registre fl. Le bit introduit à droite est 0.
Exemple : Avant exécution, ax contient (1101000101010011)2 et fl contient (0000000000000111)2 .
Après exécution de shiftl ax, ax contient (1010001010100110)2 et fl contient (0000000000000010)2 :
les bits z et e ont été remis à zéro, et le bit c contient le bit anciennement le plus à gauche de ax
qui a été perdu par le décalage.
and
Nom : and Opcode : 15
Syntaxe : and <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 3 cellules (1 pour l’opcode, 1 pour identifier le premier registre,
1 pour identifier le second registre).
Sémantique : réalise un ET logique bit à bit entre le contenu des deux registres. Le résultat est
placé dans le premier registre. Le contenu du second registre est inchangé. Après exécution, tous
les bits de fl sont mis à zéro.
Exemple : Avant exécution, ax contient (1101000101010011)2 , bx contient (0101111010101010)2 et
fl contient (0000000000000010)2 . Après exécution de and ax,bx, ax contient (0101000000000010)2 ,
bx est inchangé, fl contient (0000000000000000)2 .
or
Nom : or Opcode : 16
Syntaxe : or <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 3 cellules (1 pour l’opcode, 1 pour identifier le premier registre,
1 pour identifier le second registre).
Sémantique : réalise un OU logique bit à bit entre le contenu des deux registres. Le résultat
est placé dans le premier registre. Le contenu du second registre est inchangé. Après exécution,
tous les bits de fl sont mis à zéro.
Exemple : Avant exécution, ax contient (1101000101010011)2 , bx contient (0101111010101010)2 et
fl contient (0000000000000010)2 . Après exécution de or ax,bx, ax contient (1101111111111011)2 ,
bx est inchangé, fl contient (0000000000000000)2 .
xor
Nom : xor Opcode : 17
Syntaxe : or <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 3 cellules (1 pour l’opcode, 1 pour identifier le premier registre,
1 pour identifier le second registre).
Sémantique : réalise un OU EXCLUSIF logique bit à bit entre le contenu des deux registres.
Le résultat est placé dans le premier registre. Le contenu du second registre est inchangé. Après
exécution, tous les bits de fl sont mis à zéro.
Exemple : Avant exécution, ax contient (1101000101010011)2 , bx contient (0101111010101010)2 et
fl contient (0000000000000010)2 . Après exécution de or ax,bx, ax contient (1000111111111001)2 ,
bx est inchangé, fl contient (0000000000000000)2 .
not
Nom : not Opcode : 1A
Syntaxe : not <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules (1 pour l’opcode, 1 pour identifier le registre).
4
Sémantique : réalise un NON logique bit à bit sur le contenu du registre. Après exécution, tous
les bits de fl sont mis à zéro.
Exemple : Avant exécution, ax contient (1101000101010011)2 et fl contient (0000000000000010)2 .
Après exécution de not ax, ax contient (0010111010101100)2 et fl contient (0000000000000000)2 .
5
Nom : div Opcode : 23
Syntaxe : div <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 3 cellules (1 pour l’opcode, 1 pour identifier le premier registre,
1 pour identifier le second registre).
Sémantique : réalise la division entière des contenus des deux registres. Le résultat est placé
dans le premier registre. Le contenu du second registre est inchangé. Après exécution, le bit c
de fl contient 0, e est positionné si et seulement si une division par 0 s’est produite, et z est
positionné si et seulement si le résultat est nul. On rappelle qu’en C la division entière avec un
opérande négatif est machine dépendante. On impose, dans la division entière de SIPRO, que le
résultat soit celui de la division entière des valeurs absolues, avec le signe correctement rétablit.
6
Sémantique : Copie dans les bits de poids faible du premier registre la cellule dont l’adresse est
dans le second registre. Les autres bits du premier registre sont tous mis à zéro. Le second registre
reste inchangé. Les bits du registre fl sont tous remis à zéro après exécution de l’instruction.
storeb
Nom : storeb Opcode : 34
Syntaxe : storeb <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx, bp, sp.
Taille totale de l’instruction : 3 cellules (1 pour l’opcode, 1 pour identifier le premier registre,
1 pour identifier le second registre).
Sémantique : Copie les 8 bits de poids faible contenus dans le premier registre dans la mémoire à
l’adresse spécifiée dans le second registre. Les deux registres restent inchangés. Les bits du registre
fl sont tous remis à zéro après exécution de l’instruction.
const
Nom : const Opcode : 35
Syntaxe : const <registre>,<valeur>
Arguments : 1 identifiant de registre : ax, bx, cx, dx, bp, sp, une constante de même taille
qu’un mot machine donnée en base 10.
Taille totale de l’instruction : variable ! 1 cellule pour l’opcode, 1 cellule pour l’identifiant de
registre, 2 cellules (16 bits) pour la valeur, qui est un mot machine. L’adresse à laquelle est stockée
ce mot machine dans la mémoire doit être d’alignement valide pour la mémoire (ça doit être un
multiple de la taille d’un mot machine (2), en octets). Ainsi, la taille totale de l’instruction peut
varier de 4 à 5 cellules.
Sémantique : Copie valeur dans le registre. Les bits du registre fl sont tous remis à zéro après
exécution de l’instruction.
Exemple : Supposons que le contenu de la mémoire soit 00000035064DFF00, et que le re-
gistre IP, qui désigne l’adresse de la prochaine instruction à exécuter, contienne 03. L’opcode de
la prochaine instruction à exécuter est donc 35, qui correspond à l’instruction const. Lors de
l’éxecution de l’instruction const, le processeur va charger le contenu de la cellule suivante, qui
contient l’identifiant du registre concerné : ici 06, le registre concerné est donc cx. Le processeur
va alors demander à la mémoire la valeur du mot machine suivant immédiatement, en utilisant la
fonction de la mémoire lui permettant d’y lire un mot. L’identifiant de registre étant à l’adresse 04,
la prochaine adresse de mot machine valide (i.e. le prochain multiple de 2), est 06. Le processeur
va donc charger la constante FF00 dans le registre cx, et augmenter le pointeur d’instruction de
manière à ce qu’il désigne l’opcode de la prochaine instruction à exécuter (ici 08).
7
cause du cas particulier de push sp, qui doit sauver la valeur de sp sur la pile avant le changement
de sp. Pour exécuter cette instruction, le processeur utilise la fonction permettant d’écrire un mot
machine dans la mémoire.
pop
Nom : pop Opcode : 41
Syntaxe : pop <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx, bp, sp.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Si sp>=bp, diminue sp de la taille d’un mot machine, puis copie dans le registre
registre le mot machine contenu à l’adresse désignée par sp avant d’être diminué. Remet ensuite
les bits de fl à zéro. Bien faire attention à l’ordre, à cause du cas particulier de pop sp : après
exécution de pop sp, le registre sp doit contenir la valeur qui était au sommet de la pile avant
exécution. Pour exécuter cette instruction, le processeur utilise la fonction permettant de lire un
mot machine dans la mémoire. Si sp<bp, se contente de positionner le bit e de fl à 1.
2.3.5 Comparaison
Les instructions de comparaison permettent de comparer deux mots machines entre eux.
cmp
Nom : cmp Opcode : 50
Syntaxe : cmp <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx, bp, sp.
Taille totale de l’instruction : 3 cellules : 1 pour l’opcode, 1 pour le premier identifiant de
registre, 1 pour le second identifiant de registre.
Sémantique : Après exécution, le bit c de fl est positionné si et seulement si les deux mots
machines contenus dans les deux registres sont identiques. Le bit z de fl est positionné si et
seulement si les deux registres contiennent tous les deux la valeur 0. Le bit e de fl est toujours
remis à zéro. Ne change pas la valeur des deux registres.
uless
Nom : uless Opcode : 51
Syntaxe : uless <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx, bp, sp.
Taille totale de l’instruction : 3 cellules : 1 pour l’opcode, 1 pour le premier identifiant de
registre, 1 pour le second identifiant de registre.
Sémantique : Les mots machine contenus dans les deux registres sont interprétés comme des
valeurs non signées. Après exécution, le bit c de fl est positionné si et seulement si le contenu du
premier registre est strictement inférieur au contenu du second. Les autres bits de fl sont remis
à zéro. Ne change pas la valeur des deux registres.
sless
Nom : sless Opcode : 52
Syntaxe : sless <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx, bp, sp.
Taille totale de l’instruction : 3 cellules : 1 pour l’opcode, 1 pour le premier identifiant de
registre, 1 pour le second identifiant de registre.
Sémantique : Les mots machine contenus dans les deux registres sont interprétés comme des
valeurs signées. Après exécution, le bit c de fl est positionné si et seulement si le contenu du
premier registre est strictement inférieur au contenu du second. Les autres bits de fl sont remis
à zéro. Ne change pas la valeur des deux registres.
8
2.3.6 Branchements
Les instructions de branchement permettent de réaliser des sauts pendant l’exécution d’un
programme. On en trouve de deux catégories : celles qui s’apparentent à des goto, et celles qui
s’apparentent à des appels (et des retours) de fonctions. Attention : les instructions de saut dont
le nom commence par jmp ont toutes un comportement exceptionnel : elles ne modifient pas fl.
Le processeur SIPRO dispose de deux instructions de branchement permettant d’exécuter du
code d’une manière qu’on peut assimiler à un appel de fonction. L’instruction
permet de réaliser cet appel de fonction. L’instruction ret permet de réaliser le retour de fonction.
L’instruction qui est exécutée immédiatement après le ret est celle suivant immédiatement le
call ayant appelé la fonction. Le mécanisme permettant de retrouver, après un ret, l’adresse de
l’instruction suivant le call est le suivant. L’instruction call utilise la pile : pendant son exécution,
le processeur place en sommet de pile l’adresse de l’instruction qui suit immédiatement le call.
Pour exécuter l’instruction ret, le processeur récupère cette adresse, qu’il suppose toujours au
sommet de la pile. Il connait ainsi l’adresse de la prochaine instruction à exécuter après le ret.
Attention cependant : pour la bonne marche de ce procédé, le programmeur ne doit pas avoir
retiré par inadvertance cette adresse de la pile, ni ajouté quelque chose en sommet de pile pendant
la fonction (ou alors, il a retiré ce quelque chose avant le ret). Au début de l’exécution de la
fonction, i.e. juste après l’éxécution de l’instruction call, le sommet de la pile contient l’adresse
de l’instruction qu’il faudra exécuter après le ret. À la fin de la fonction, juste avant d’exécuter
l’instruction ret, le sommet de la pile est supposé contenir cette même valeur.
Le passage d’arguments aux fonctions peut se faire de deux manières différentes. C’est au
programmeur de choisir celle qui lui convient le mieux. La première manière consiste à placer
les valeurs qu’on veut passer en argument à la fonction dans des registres juste avant l’appel.
La fonction récupèrera alors ces valeurs dans les registres. Cette façon de procéder suppose que
le nombre de paramètres est plus petit ou égal au nombre de registres. La seconde manière de
procéder utilise la pile : avant d’appeler la fonction, on met les arguments sur la pile. La fonction
ira alors récupérer ces valeurs. Attention, on rappelle qu’au début de l’exécution de la fonction, le
sommet de la pile contient l’adresse de retour, et il faut que cette valeur se trouve également au
sommet de la pile juste avant le ret. Aussi, en général, pour récupérer les arguments dans la pile,
qui sont sous cette adresse de retour, on n’utilise pas l’opération de dépilement : on va directement
lire le contenu de la pile, sans dépiler.
Le passage de la valeur de retour peut se faire de manière similaire au passage des arguments.
Cependant, comme la valeur de retour est en général unique, pour plus de simplicité le rpogram-
meur peut se fixer une convention du style : une fonction place sa valeur de retour dans le registre
ax avant le ret.
L’exemple donné plus loin dans le sujet (calcul de factorielle récursif) illustre ce mécanisme
d’appel de fonction en lui passant ses arguments par l’intermédiaire de la pile. Juste vant d’exécuter
la première instrution de la fonction factorielle, au sommet de pile se trouve l’adresse de
l’instruction qui suit immédiatement le call ayant exécuté la fonction, et juste sous cette adresse
se trouve dans la pile l’argument de la fonction. Les toutes première instruction de la fonction ont
pour objet de récupérer cet argument. On observe que pour ce faire on n’utilise pas d’opération
de dépilement. À son retour, le résultat de la fonction factorielle se trouve dans le registre ax.
Afin de rendre l’émulateur réellement utilisable, des opérations de branchement particulières
permettant de réaliser des entrées/sorties du genre scanf ou printf ont été ajoutées. Ces fonctions
n’existent pas dans un processeur réel : c’est au programmeur de les écrire, et elles sont très
complexes. Dans l’implantation de l’émulateur, l’exécution d’une de ces instructions revient tout
simplement à faire un printf ou un scanf, en prenant éventuellement quelques précautions.
Les instructions de branchement sont également appelées “instructions de saut”.
jmp
9
Nom : jmp Opcode : 60
Syntaxe : jmp <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Copie le contenu de registre dans ip.
jmpz
Nom : jmpz Opcode : 61
Syntaxe : jmpz <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Copie le contenu de registre dans ip si le bit z de fl est positionné. Ne change
ni la valeur du registre, ni fl.
jmpc
Nom : jmpc Opcode : 62
Syntaxe : jmpc <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Copie le contenu de registre dans ip si le bit c de fl est positionné. Ne change
ni la valeur du registre, ni fl.
jmpe
Nom : jmpe Opcode : 63
Syntaxe : jmpe <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Copie le contenu de registre dans ip si le bit e de fl est positionné. Ne change
ni la valeur du registre, ni fl.
call
Nom : call Opcode : 65
Syntaxe : call <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Fait l’équivalent d’un push ip+2 (même si cette dernière instruction n’existe
pas, on comprend ce qu’elle réalise), puis copie le contenu de registre dans ip. Ne modifie pas
la valeur du registre. Remet tous les bits de fl à zéro.
ret
Nom : ret Opcode : 66
Syntaxe : ret
Arguments : aucun
Taille totale de l’instruction : 1 cellule.
Sémantique : Dépile un mot machine, et se branche à l’adresse désignée par ce mot pour
l’exécution de la prochaine instruction. Ne modifie pas les bits de fl. Pour exécuter cette instruc-
tion, le processeur utilise la fonction qui permet de lire un mot machine dans la mémoire à une
adresse donnée.
callprintfd
Nom : callprintfd Opcode : 6A
Syntaxe : callprintfd <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Réalise l’affichage de la valeur du mot machine dont l’adresse est dans registre,
sous la forme d’un entier signé. Ne modifie pas la valeur du registre. Remet tous les bits de fl à
10
zéro. Pour exécuter cette instruction, le processeur utilise une fonction qui permet de lire/écrire
un mot machine dans la mémoire à une adresse donnée.
callprintfu
Nom : callprintfu Opcode : 6B
Syntaxe : callprintfu <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Réalise l’affichage de la valeur du mot machine dont l’adresse est dans registre,
sous la forme d’un entier non signé. Ne modifie pas la valeur du registre. Remet tous les bits de fl
à zéro. Pour exécuter cette instruction, le processeur utilise une fonction qui permet de lire/écrire
un mot machine dans la mémoire à une adresse donnée.
callprintfs
Nom : callprintfs Opcode : 6C
Syntaxe : callprintf <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Réalise l’affichage de la chaı̂ne de caractères dont l’adresse est dans registre.
Ne modifie pas la valeur du registre. Remet tous les bits de fl à zéro.
callscanfd
Nom : callscanfd Opcode : 6D
Syntaxe : callscanfd <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Réalise une saisie de valeur de mot machine au clavier, sous la forme d’un entier
signé. Positionne le bit e de fl si et seulement si la saisie s’est mal passée ou la valeur est en dehors
des intervalles représentables pour un mot machine de SIPRO. Les autres bits de fl sont remis à
zéro. Après exécution, la valeur saisie se trouve à l’adresse contenue dans registre. Pour exécuter
cette instruction, le processeur utilise une fonction qui permet de lire/écrire un mot machine dans
la mémoire à une adresse donnée.
callscanfu
Nom : callscanfu Opcode : 6E
Syntaxe : callscanfu <registre>
Arguments : 1 identifiant de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 2 cellules : 1 pour l’opcode, 1 pour l’identifiant de registre.
Sémantique : Réalise une saisie de valeur de mot machine au clavier, sous la forme d’un entier
non signé. Positionne le bit e de fl si et seulement si la saisie s’est mal passée ou la valeur est
en dehors des intervalles représentables pour un mot machine de SIPRO. Les autres bits de fl
sont remis à zéro. Après exécution, la valeur saisie se trouve à l’adresse contenue dans registre.
Pour exécuter cette instruction, le processeur utilise une fonction qui permet de lire/écrire un mot
machine dans la mémoire à une adresse donnée.
callscanfs
Nom : callscanfs Opcode : 6F
Syntaxe : callscanfs <registre>,<registre>
Arguments : 2 identifiants de registre : ax, bx, cx, dx.
Taille totale de l’instruction : 3 cellules : 1 pour l’opcode, 1 pour le premier identifiant de
registre, 1 pour le second identifiant de registre.
Sémantique : Réalise une saisie de chaı̂ne de caractères au clavier. Positionne le bit e de fl si
et seulement si la saisie s’est mal passée (aucun caractère n’a pû être lu). Les autres bits de fl
sont remis à zéro. Après exécution, la chaı̂ne de caractère saisie se trouve à l’adresse contenue dans
le premier registre. Elle se termine par un caractère de fin de chaı̂ne. Le second registre contient
le nombre de caractères maximum qui peuvent être saisis, y compris le caractère de fin de chaı̂ne.
11
Contrairement au comportement de la fonction de la bibliothèque standard C fgets, l’éventuel
caractère \n n’est pas stocké dans la chaı̂ne résultat.
nop
Nom : nop Opcode : 00
Syntaxe : nop
Arguments : aucun
Taille totale de l’instruction : 1 cellule.
Sémantique : Ne fait rien, sauf remettre les bits de fl à zéro.
end
Nom : end Opcode : FF
Syntaxe : end
Arguments : aucun
Taille totale de l’instruction : 1 cellule.
Sémantique : Termine le programme.
12
— les directives de placement de chaı̂nes de caractères constantes dans la mémoire ;
— les directives de placement de mots machines dans la mémoire.
Une directive de placement de chaı̂ne de caractère a la forme : @string "Bonjour". L’assem-
bleur la comprend comme : prendre le code ASCII de chaque lettre, et les mettre dans l’ordre dans
le fichier de sortie, avec un caractère de fin de chaı̂ne (de code ASCII 0).
Une directive de placement d’entier a la forme : @int -1234. L’assembleur la comprend comme :
coder -1234 en mot machine, et écrire ce mot machine dans le fichier de sortie. En cas d’erreur,
comme par exemple une constante trop grande, l’assembleur affiche un message d’erreur, le numéro
de ligne et arrête son travail.
L’assembleur doit toujours s’arranger pour placer les constantes dans la mémoire à une adresse
compatible avec l’alignement des entiers (même pour les chaı̂nes de caractères).
4 Exemple complet
Nous développons dans cette section un exemple complet, qui comprend :
— le code d’un programme dans le langage d’assemblage, commenté et expliqué ;
— le contenu expliqué du fichier de sortie de l’assembleur.
L’exemple suivant demande un entier à l’utilisateur, calcule sa factorielle de manière récursive,
puis l’affiche.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Début de la zone de stockage des constantes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
:chaine1
@string "Rentrez votre nombre:"
13
:chaine2
@string "Factorielle "
:chaine3
@string " vaut "
:chaine4
@string "\n"
:chaine5
@string "Erreur !"
:valeurinit
@int 0
:resultat
@int 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fin de la zone de stockage des constantes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;
; Début réel du code
;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fonction de saisie
; La fonction ne modifie aucun registre, mis à part ax,
; qui au retour de la fonction contient la valeur saisie
; On commence par sauver tous les registres, de cette manière la fonction
; peux les modifier à loisirs, et restaurer les valeurs d’origine avant de
; retourner à la fonction appelante
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
:saisie
push sp
push bp
push bx
push cx
push dx
; On affiche chaine1
const ax,chaine1
callprintfs ax
; On saisie une valeur, qu’on va mettre à l’adresse spécifiée par valeurinit
const bx,erreur
const ax,valeurinit
callscanfu ax
; en cas d’erreur on appelle la fonction de sortie sur erreur
jmpe bx
; on va maintenant récuperer la valeur stockée à l’adresse valeurinit (désignée par ax)
; pour la mettre dans le registre ax
cp bx,ax
loadw ax,bx
; On restaure les registres avant de sortir
; On fait bien attention à faire les pop
; dans l’ordre inverse des push du début de la fonction
pop dx
pop cx
pop bx
pop bp
pop sp
; On retourne à la fonction appelante
ret
14
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fin de la fonction de saisie
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fonction calculant récursivement la factorielle du mot machine
; se trouvant sous le sommet de la pile
; L’argument se trouve sous le sommet de la pile
; En effet le sommet de la pile contient l’adresse de retour
; de la fonction
; Le résultat est mis dans ax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
:factorielle
; On commence par calculer dans cx l’adresse de l’argument
cp cx,sp
const bx,2
sub cx,bx
; C’est fait, maintenant on met la valeur de l’argument dans ax
loadw ax,cx
; on regarde si on est dans le cas de base
xor bx,bx
const dx,casdebase
cmp ax,bx
jmpz dx
; on est dans le cas général
const bx,1
sub ax,bx
push ax
const dx,factorielle
call dx
pop dx
; La valeur de retour est dans ax
; On récupère de nouveau la valeur de l’argument
cp dx,sp
const bx,2
sub dx,bx
loadw bx,dx
mul ax,bx
const dx,finfact
jmp dx
:casdebase
const ax,1
const dx,finfact
jmp dx
:finfact
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fin de la fonction factorielle récursive
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fonction de sortie sur erreur
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
:erreur
const ax,chaine5
callprintfs ax
end
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15
; Fin de la fonction de sortie sur erreur
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;
; Fonction principale
;;;;;;;;;;;;;;;;;;;;;
:debut
const bp,pile
const sp,pile
const ax,2
sub sp,ax
const dx,saisie
call dx
const bx,valeurinit
storew ax,bx
const bx,chaine2
callprintfs bx
const bx,valeurinit
callprintfu bx
const bx,chaine3
callprintfs bx
push ax
const bx,factorielle
call bx
pop bx
const bx,resultat
storew ax,bx
callprintfu bx
const bx,chaine4
callprintfs bx
end
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fin de la fonction principale
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Début de stockage de la zone de pile
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
:pile
@int 0
16
fonction demande à l’utilisateur de rentrer un entier naturel au clavier. Par convention, au retour
de la fonction, cet entier est placé dans le registre ax. Les deux instructions suivantes permettent
de placer le contenu de ax (donc, la valeur rentrée au clavier par l’utilisateur), dans l’emplace-
ment mémoire labelisé valeurinit. On appelle ensuite trois fonctions d’affichage, qui réalisent
respectivement l’affichage de
— “Factorielle ”
— la valeur rentrée par l’utilisateur
— “ vaut ”
Puis on appelle la fonction factorielle. Par convention, on place son argument (contenu dans le
registre ax) dans la pile avant de l’appeler. Au retour de la fonction factorielle, le résultat de
son calcul est par convention dans le registre ax. Comme on a placé une valeur dans la pile avant
d’appeler la fonction factorielle, on prend soin de retirer cette valeur de la pile après l’appel de
la fonction, car cette dernière ne sert plus, et il convient de ne pas laisser dans la pile de valeurs
inutiles. On stocke ensuite la valeur de retour dans l’emplacement mémoire labelisé par resultat,
et on l’affiche. On affiche ensuite un caractère de retour à la ligne, et le programme est terminé.
La fonction de saisie étant suffisamment commentée, nous n’insistons pas. Nous passons à
l’explication de la fonction factorielle. Cette fonction prend un argument entier qu’on lui fourni
par l’intermédiaire de la pile, et calcule récursivement sa factorielle. Avant de terminer, elle met
le résultat dans le registre ax. Les quatre premières instructions de la fonction servent à récupérer
son argument. Ce dernier a été mis dans la pile juste avant qu’on appelle la fonction par un call.
Le call place l’adresse de l’instruction à exécuter immédiatement après la fonction factorielle
dans la pile (on appellera cette adresse adresse de retour ), donc au dessus de l’argument de la
fonction. Cette adresse d’instruction se trouvant au sommet de la pile est donc désignée par sp,
et l’argument se trouve à l’adresse sp-2, 2 étant la taille d’un mot machine exprimé en cellules. Il
existe deux méthodes pour récupérer l’argument : ou bien on dépile l’adresse de retour, on dépile
l’argument, et on remet l’adresse de retour sur la pile, ou bien va lire directement dans la pile,
en dessous de son sommet. C’est cette seconde méthode qui a été choisie. Une fois l’adresse de
l’argument calculée, on récupère sa valeur, qu’on place dans le registre ax. On passe maintenant
aux instructions suivantes, qui regardent si on est dans le cas de base (argument vaut zéro) ou
pas. L’instruction xor bx,bx met 0 dans bx (faites-le à la main si vous n’en êtes pas convaincu).
Ensuite on compare l’argument à bx (donc à 0), et si le résultat de la comparaison est positif
(égalité) on saute à l’instruction labelisée casdebase : on met 0! = 1! dans ax, et on retourne à
la fonction appelante. Si on est pas dans le cas de base, on est dans le cas général. On retire 1
à l’argument (toujours contenu dans ax). On note que la valeur initiale de l’argument se trouve
encore dans la pile, sous l’adresse de retour : on pourra l’y retrouver si nécessaire). Une fois ax
diminué de 1, on va faire un appel récursif à factorielle. On place son argument dans la pile,
puis on l’appelle. Au retour, la valeur que cet appel aura calculé se trouvera dans ax. Comme on
a placé une valeur dans la pile avant d’appeler récursivement la fonction factorielle et qu’on a
plus besoin de cette valeur, on la retire de la pile pour laisser cette dernière dans un état propre.
Il faut maintenant faire le produit de la valeur de retour de l’appel récursif (qui se trouve dans
ax) et de l’argument de la fonction (qui lui se trouve, rappelons-le encore une fois, dans la pile
sous l’adresse de retour). Comme en début de fonction, on calcule l’adresse du mot machine se
situant immédiatement sous le sommet de la pile, et on récupère ce mot machine, en stockant sa
valeur dans bx. On fait le produit de bx et ax, l’instruction mul met son résultat dans son premier
argument, ici ax. Le calcul est terminé, la fonction se termine.
Voici le contenu du fichier [Link] en sortie du programme d’assemblage correspondant au
programme précédent écrit dans l’assembleur de SIPRO. Ce contenu a été obtenu en utilisant
la commande unix od -x [Link]. La première colonne est un numéro donné en octal. Ensuite
viennent une suite de 8 mots de 16 bits, affichés en hexadécimal. Attention, le premier octet
du mot est affiché en seconde position. Par exemple, la première instruction du programme est
const ax,debut. L’instruction const a pour opcode 35 (base 16). Ensuite vient un octet iden-
tifiant le registre opérande de const : ici ax, l’octet vaut 4. Les deux premiers octets du fichier
contenant le programme compilé sont donc dans l’ordre 35 et 4. La commande od les a affichés
17
dans l’ordre inverse. Bien entendu il en est de même pour les autres mots.
La même commande, avec l’option -c, permet d’interpréter chaque octet comme étant un
caractère, et donne :
On voit nettement apparaı̂tre les chaı̂nes de caractères constantes utilisées dans le programme.
C’est ce fichier [Link] (ou d’un autre nom) que l’émulateur lira et interprétera comme un
programme qu’il exécutera.
18