Le langage d'assemblage du Motorola 68010 : Petite présentation

Version 1.01a

Table des matières



Liens

Le coin technique d'Ulysse, qui regroupe bon nombre de documents techniques


Introduction

L'objectif de cette page est de vous permettre de vous familiariser assez vite avec le langage d'assemblage de la série 680x0 (et par là même à la structure de tous les langages de d'assemblage).

Ensuite, nous aborderons progressivement en détail les premières notions à maîtriser sur le processeur 68010.

1. Le microprecesseur ou l'unité centrale de traitement (CPU, Central Processing Unit)

1.1. C'est quoi ?

Le microprocesseur d'un ordinateur est la partie centrale de celui-ci. C'est à travers lui et avant tout autre chose que les programmes s'exécutent. Le microprocesseur cependant ne connaît qu'un seul langage : celui qui lui est propre, le langage machine. On donne communément le nom du processeur comme nom du langage machine qu'il comprend. Le 68010, par exemple, comprend donc... le 68010. De plus, les processeurs sont généralement compatibles avec leurs prédécesseurs ; dans le cas présent, le 68000.

1.2. Qu'est-ce que le langage machine ?

Le langage machine est l'association de séquences d'octets à des actions (bien-sûr seul un certain nombre de séquences ont un sens). Pour mieux comprendre, revenons au fondement de ce qu'est un programme.

Un programme est une association entre code (instructions ou actions) et données. Plus précisement, un programme est une séquence d'instructions qui vont être exécuter à partir d'une mémoire à partir d'une adresse de départ et dont les instructions vont interagir plus ou moins directement avec les données se trouvant aussi en mémoire. De plus, certaines instructions permettent de briser le flot d'instructions pour le brancher en une autre adresse (avec les instructions de branchements ou de sauts, ainsi qu'avec les instructions d'appels à des sous-routines ou sous-programmes) ; d'autres permettent de contrôler le mode de fonctionnement du microprocesseur (comment le CPU doit-il réagir aux évènements extérieurs par exemple).

 
 
CPU (Microprocesseur)
 
Memory (Mémoire)
 
BUS (Ensemble de lignes (adresse et données) reliant le processeur et la mémoire permettant de transférer des données de tailles variables d'une ou vers une adresse de la mémoire)
 
 

Sans rentrer dans les détails, une instruction est une très courte séquence d'octets dont une partie renseigne le CPU sur l'action à effectuer (OpCode, Operation Code) et dont une autre partie éventuelle renseigne sur quelles données faire porter l'action quand ce n'est pas déjà sous-entendu (opérandes).

1.3. Comment programmer un microprocesseur ? (Le langage d'assemblage)

Il est bien-sûr théoriquement possible de programmer en langage machine, mais on a trouvé un moyen plus facile pour un être humain de manipuler ces instructions : leur traduction en langue humaine en utilisant des mnémoniques, c'est-à-dire que l'on donne des noms aux instructions en rapport avec l'action effectuée. Il s'agit du langage d'assemblage. Bien-entendu, comme il y a un langage machine par CPU, il y a aussi un langage d'assemblage par CPU et bien-sûr on retrouve les mêmes compatibilités dans cette dernière catégorie de langages que dans la précédente. Le langage d'assemblage du 68010, par exemple, permet donc de programmer en langage d'assemblage du 68000. Ensuite, on utilise un programme qui permet de traduire les sources écrits en langage d'assemblage vers le langage machine ; un tel programme s'appelle un assembleur.

N.B. :

Il est dans l'usage de pratiquer l'abus de langage qui consiste à employer le terme assembleur à la place de l'expression langage d'assemblage. Essayez dans tous les cas néanmoins de ne pas confondre ces deux notions. Le langage d'assemblage (en anglais : assembly language ou assembly) est le langage de programmation dans lequel on écrit le programme, alors que l'assembleur (en anglais : assembler) est le programme qui traduit le source écrit en langage d'assemblage vers le langage machine.

1.4. Comment manipuler les données dans une instruction en langage d'assemblage ? (Les opérandes)

La première idée que l'on pourrait avoir est de donner l'adresse en mémoire des données à utiliser. Cette solution est malheureusement mauvaise. On a plutôt recours à ce que l'on appelle les registres (en anglais : registers). Les registres sont de petits espaces mémoires directement situés dans le CPU et donc directement accessibles (c'est fait pour !!) par les instructions du CPU. Mais, pourquoi l'accès direct à la mémoire est-il une mauvaise solutions ? Pour plusieurs raisons :

Vous vous demandez donc du coup, comment on fait pour mettre des données dans les registres si on peut pas faire d'accès direct à la mémoire. Et bien non ! On peut évidemment faire des accès à la mémoire, c'est juste qu'ils ne sont pas recommendés. Il existe donc des instructions permettant de transférer des données des registres vers la mémoire et vis-versa. Il est même possible de charger une constante (valeurs immédiates) dans un registre sans la charger de la mémoire ; mais comment me direz-vous ? En codant cette constante directement dans l'intruction en langage machine (à côté de l'OpCode et des opérandes). Notez néanmoins que contrairement à ce que je laissais présentir, les processeurs tels que le 68010 (qui sont CISC) sont très souvent capables d'utiliser des opérandes mémoires à la place des registres dans beaucoup d'autres instructions que les deux instructions de transfert citées ci-dessus. Néanmoins, certains processeurs n'offrent pas cette possibilité supplémentaire (il s'agit des processeurs de type RISC).

La suite portera donc sur les instructions de base existant effectivement sur le 68010 (vous trouverez la liste complète dans le poly #5 (2001-2002) ou poly #11 (2000-2001)), et sur la syntaxe des opérandes en particulier dans le cas des opérandes mémoires ; ceci après une brève présentation des registres du 68010.

N.B. :

CISC signifie Complex Instruction Set Computer, alors que RISC signifie Reduced Instruction Set Computer. La différence est la suivante : Les processeurs CISC ont en général peu de registres, des instructions dont la taille varie afin de réduire la taille du code et des instructions qui savent réaliser des tâches un peu complexe car si on les réalisait avec plusieurs instructions, cela prendrait plus de temps, donc certains se sont dit que cela valait le coup de les microcoder directement. Ce type de philosophie correspond à une époque où la mémoire coutait chère et les processeurs n'étaient dans l'absolu pas assez rapide. Les processeurs RISC se fonde sur l'observation suivante : si l'on veut accélérer la cadence d'un processeur il faut réduire sa taille et par conséquent le simplifier, c'est-à-dire que l'on espère gagner plus de vitesse par l'architechture et la fréquence que par la complexité des instructions. Ils ont donc beaucoup de registres pour minimiser les accès mémoire, que des opérations simples qui n'utilisent que les registres (pas d'accès mémoire) sauf pour les deux opérations de transfert, et la taille du code est standardisée pour accélérer le chargement du code, puisqu'il est aligné, c'est-à-dire permettant le plus petit nombre d'accès mémoire pour charger l'instruction dans le CPU. En effet, la mémoire étant moins chère, la taille des instructions est devenue une préoccupation moindre.

2. Les registres

Réferrez-vous au paragraphe 1.4. pour la définition des registres. Le 68010 possèdent 16 registres 32-bits à usage général (en anglais : general purpose registers) divisés en deux groupes de 8, outre quelques autres registres dont voici une liste (dont je n'assurerai pas l'exhaustivité) :

PC (Program Counter) :
Contient l'adresse de l'instruction en cours d'exécution. C'est le registre qu'utilise le CPU pour charger les instructions au fur et à mesure.
SR (System Flags Register) et CCR (Condition Code Register) :
Contient un certain nombre de bits décrivant l'état du CPU. Les 8-bits inférieurs de SR (aussi appelés, registre CCR) contiennent des bits renseignant sur le résultat des dernières instructions. Vous y trouvez entre autre les bits C (Carry ou retenue), Z (Zero), N (Negative ou négatif), V (Overflow ou dépassement de capacité), dont la valeur dépend de la dernière opération qui les a modifiés. C passe à 1 entre autre si une addition fait déborder un registre par au dessus (Par exemple, avec des registres 32-bits : 0xFFFFFFFF + 0x00000002 = 0x100000001 résultat sur 33-bits) ou si une soustraction donne un résultat inférieur à zéro. Z passe à 1 quand le résultat d'une opération donne zéro. N passe à 1 lorsque le résultat d'une opération dans un nombre, dont la représentation en binaire signée est négative. V passe à 1 lorsque par exemple une addition de deux nombres de même signe, considérant la représentation binaire signée, donne un résultat de signe opposé (ce qui n'est pas normal dans une addition, vous en conviendrez, non ?). Par exemple, avec des registres 8-bits en considérant (nous, humains) la représentation binaire signée : 0x7F (127) + 0x02 (2) = 0x81 (-127). Bien entendu, vous êtes libre de ne pas tenir compte des bits qui ne vous intéressent pas : c'est vous le maître. Si pour vous 0x81 vaut 129, c'est votre choix, le CPU ne fait que de vous donner des informations en fonction des représentations signée ou non signée que vous pourriez décider de voir dans les valeurs des registres. C'est à vous d'utiliser ces informations comme bon vous semble. Par exemple, vous pouvez très bien voir un registre comme un champ de bits, par conséquent, les bits C, N et V risquent d'avoir peu d'utilité.
Registres à usage général (32-bits)
Registres de données Registres d'adresses
D0 A0
D1 A1
D2 A2
D3 A3
D4 A4
D5 A5
D6 A6
D7 A7 ou (USP (A7)/SSP (A7')), le(s) pointeur(s) de pile d'exécution.

Dorénavant, je noterai R (ou Rx) une opérande, si elle peut être de type registre, si nécessaire D (ou Dx) pour les registres de données et A (ou Ax) pour les registres d'adresses, et par ailleurs, je noterai I une opérande, si elle peut être une valeur immédiate (une constante).

Les registres de données et d'adresse peuvent être utilisés partiellement. On pourra utiliser les bits de poids faible suivant :

Pour les registres de données Dx :
Les 8-, 16- ou 32-bits de poids faible.
Pour les registres d'adresse Ax :
Les 16- ou 32-bits de poids faible.

Il s'avèrera nécessaire dans certains cas d'ajouter à un registre un suffixe .W ou .L, si l'on voudra utiliser respectivement les 16-bits de poids faible ou l'ensemble des 32-bits du registre. Le cas échéant, ce sera dit explicitement.

N.B. :

Le 68010 peut fonctionner dans deux modes opératoires, le mode superviseur ou le mode utilisateur. Dans le premiers, l'exécution de toutes les instructions est autorisée, alors que dans le second, cetaines sont interdites et provoquent des exceptions. Sans s'étendre, cela permet à un système qui tournerait en mode superviseur de faire tourner les applications utilisateurs en mode utilisateur de telle sorte qu'elles ne puissent pas faire ce qu'elles veulent. Le point que je tiens à expliquer ici est l'existence de deux pointeurs de pile d'exécution. En fait, il y en a un pour chaque mode : SSP pour le mode superviseur et USP pour le mode utilisateur. Notez seulement que dans l'utilisation courante du CPU, vous n'avez pas à vous soucier duquel vous vous servez car c'est invisible. Vous n'auriez à vous soucier de ceci que si vous voulez entrer dans des considérations de programmation système ce qui n'est a priori pas votre cas. Utilisez donc le registre A7 par le nom "A7" sans réfléchir.

3. La syntaxe du langage d'assemblage 68010

3.1. Les règles générales

Un source en langage d'assemblage est divisé en lignes, chacune d'elle peut soit être vide, soit avoir un commentaire qui commence par une astérisque '*' et se termine par le saut à la ligne, soit avoir une directive directement suivi d'un commentaire éventuel (l'astérisque n'est pas nécessaire ici), soit enfin avoir une vrai instruction éventuellement précédée d'un label et éventuellement suivie d'un commentaire. Il y a néanmoins quelques règles supplémentaires à respecter, surtout si l'on ne veut pas passer 3 heures à tabuler un source de 2000 lignes... Enfin, un source se termine par une directive end (il ne s'agit pas de la fin du programme, mais de l'endroit où l'assembleur arrêtera d'assembler, autrement dit, ce qui suit la directive end est du commentaire).

Je permets une petite parenthèse qui n'a pas avoir avec le langage d'assemblage lui-même, mais plutôt à l'utilisation de programmes avec les simulateur de microprocesseurs : pour terminer un programme, c'est-à-dire qu'il arrête de tourner, il suffit d'exécuter l'instruction TRAP #15.

3.2. La syntaxe d'une instruction

Une instruction commence par son mnémonique (MOVE, ADD, AND, BRA...) qui est éventuellement concaténé avec un suffixe indiquant la taille des données manipulées, lorsque l'instruction en manipule (.L 32-bits (long ou double word), .W 16-bits (word), .B 8-bits (byte)). Ensuite, vient la liste d'opérandes, dont les opérandes sont séparées par des virgules (souvenez-vous, il ne faut pas insérer d'espaces ou de tabulations dans la liste d'opérandes). Une opérande se classe parmi 3 catégories : les opérandes Registres (quand on cherche à manipuler le contenu d'un registre), les opérandes Mémoires (quand on cherche à manipuler le contenu de la mémoire à une adresse donnée, adresse qui peut alors éventuellement être calculée avec le contenu de certains registres qu'il faudra choisir), et les opérandes Immédiates, autrement dit les constantes (qui sont directement encodées à l'intérieur de l'instruction en langage machine). Dans le code, les valeurs immédiates sont précédées d'un dièse '#'.

Exemples :

Les exemples de lignes de codes présentées ci-dessous sont des sources incomplets. En particulier, il manque systématiquement la directive end qui doit terminer tout source en langage d'assemblage du 68000. Je n'utilise pas non plus le "TRAP #15" qui termine un programme.

Vous aurez constaté que je n'ai donné aucun exemple jusqu'à maintenant avec des instructions utilisant des opérandes mémoires. Au pire, on a chargé des adresses, mais jamais des données sur lesquelles elles pointaient. Nous allons donc voir les opérandes mémoires maintenant à part, étant donné qu'il existe beaucoup de moyens d'adresser des données en mémoire.

3.3. Les adressages des opérandes mémoires (ou les différentes manières d'écrire une adresse effective)

Pour accéder à une donnée en mémoire, il faut que le processeur connaisse l'adresse de cette donnée. Il faut donc qu'elle soit soit directement encodée dans l'instruction en langage machine, soit qu'une manière de la calculer (avec une éventuelle constante et des choix de registres) y soit encodée. L'adresse encodée s'appelle l'adresse effective. Nous allons voir la syntaxe particulière pour l'ensemble des manières d'adresser une donnée en mémoire, c'est-à-dire les adressages.


Adressage par déplacement (adresse fournie sous forme d'une constante)

Exemples :
c1	equ	$108
	org	$0	* Ce qui suit commence à l'adresse $0.
	MOVE.L	$104,D0
			* La donnée 32-bits se situant à l'adresse $104 (260)
			* est copiée dans D0, écrasant l'ancienne valeur de
			* D0. La mémoire est inchangée. En l'occurence la
			* donnée copiée vaut $C0 (regardez la directive
			* org qui précède). CCR est modifié...
			* Rappel : Les registres sont dans le CPU, pas
			* en mémoire !! Quand je dis que "la mémoire est
			* inchangée", je parle bien-sûr de l'opérande source,
			* c'est-à-dire l'opérande mémoire, ou bien encore la
			* donnée 32-bits se situant à l'adresse $104.
	MOVE.L	m1,D1
			* La donnée 32-bits se situant à l'adresse de la
			* valeur du symbole m1, c'est-à-dire $100
			* (regardez la directive org qui précède),
			* est copiée dans D1. En l'occurence la donnée copiée
			* vaut $40. CCR est modifié...
	MOVE.L	c1,D2
			* La donnée 32-bits se situant à l'adresse de la
			* valeur de la constante (donc du symbole - synonyme)
			* c1, c'est-à-dire $108 est copiée dans D2. En
			* l'occurence la donnée copiée vaut $60. CCR est
			* modifié...
	MOVE.L	m1+2*2,D3
			* La donnée 32-bits se situant à l'adresse de la
			* valeur de l'expression (qui sera calculée avant
			* d'être encodée en langage machine par l'assembleur)
			* m1+2*2, c'est-à-dire $104 est copiée vaut D3. En
			* l'occurence la donnée copiée vaut $C0.
	org	$100	* Ce qui suit commence à l'adresse $100
m1	dc.l	$40	* 32-bits à l'adresse $100
	dc.l	$C0	* 32-bits à l'adresse $104 (32-bits = 4 octets)
	dc.l	$60	* 32-bits à l'adresse $108

Le mode d'adressage par déplacement (displacement) permet d'accéder directement à une donnée en mémoire, en utilisant tout simplement l'adresse où elle est stockée. Notez que le déplacement pourra être encodé dans l'instruction en langage machine soit avec 32-bits (même si seuls 24-bits servent), soit 16-bits signés (tels que l'adresse effective soit dans la fourchette 0xFF8000 - 0x007FFF en passant par 0).

ATTENTION !!

Ne confondez pas ce mode d'adressage qui sert à accéder à une donnée en mémoire avec la syntaxe permettant d'écrire des valeurs immédiates. Les valeurs immédiates sont toujours précédées d'un dièse '#'.

N.B. :

Le coin de Lea :

Il existe une instruction qui risque de vous déranger ; il s'agit de l'instruction LEA. Elle permet d'initialiser un registre d'adresse avec une adresse effective calculée selon l'un des modes d'adressage dont on vient d'en voir un. Par conséquent, elle n'accède pas à la mémoire, bien qu'il y ait une opérande mémoire en jeu. Notez-bien que c'est un cas particulier ; LEA sert à ça (Load Effective Address). Par conséquent, seulement parce que c'est le travail de cette instruction et si vous voulez initialiser un registre avec une adresse exprimée avec le mode d'adressage par déplacement que l'on vient de voir, il ne faudra donc pas mettre de dièse, puisqu'il s'agit d'une adresse et pas d'une valeur immédiate dans l'encodage de l'instruction en langage machine. C'est l'encodage en langage machine qui prime !

Exemples :

Vous comprendrez plus l'interêt de l'instruction LEA, lorsque vous connaîtrez d'autres modes d'adressage (L'intérêt de pouvoir charger dans un registre une adresse effective dont le calcul est complexe en une seule instruction, pour pouvoir sauvegarder l'adresse d'une case d'un tableau par exemple...). Je vous dirai quelques mots sur LEA à chaque mode d'adressage.


Adressage indirect par registre (base)

Le mode d'adressage indirect par registre permet d'accéder à une donnée en mémoire en calculant l'adresse effective à partir d'un registre d'adresse Ax. La syntaxe consiste à écrire l'opérande avec le registre entouré par des parenthèses : "(Ax)". Il faut donc bien-sûr s'assurer que le registre d'adresse choisi contienne bien l'adresse souhaité avant de l'utiliser dans une instruction ayant une opérande mémoire utilisant le mode d'adressage indirect par registre avec ce registre, sinon vous risquez d'accéder à n'importe quoi... Segmentation fa... :)

Exemple :
	org	$0
	MOVEA.L	#$100,A0
			* Initialise A0 avec la valeur immédiate
			* "#$100".
	MOVE.L	(A0),D0
			* Initialise D0 avec la donnée dont l'adresse est
			* l'adresse effective de l'opérande source, donc la
			* valeur du registre A0. L'adresse effective "(A0)"
			* vaut donc $100, par conséquent D0 vaut $80 après
			* l'exécution de l'instruction. CCR est modifié...
			* En C, on aurait pu dire que A0 joue le rôle de
			* pointeur.
	org	$100
	dc.l	$80

Le coin de Lea :

Vous vous rappelez ? Je vous avez dit que je vous dirai quelques mots sur LEA à chaque mode d'adressage, et bien nous y voilà ! Rappel : LEA est une instruction qui charge dans un registre d'adresse une adresse effective et qui n'accède absolument pas à la mémoire, comme le font les autres instructions utilisant des adresses effectives. Je vous rappelle que c'est un cas particulier : c'est le travail de LEA (Load Effective Address). Donc faites attention à ne pas vous tromper losrque vous utiliserez LEA.

Exemples :

Adressage indirect par registre (base) et déplacement

Le mode d'adressage indirect par registre et déplacement permet d'accéder à une donnée en calculant son adresse effective à partir de la somme d'un registre d'adresse 32-bits (appelé base dans ce contexte) et d'un déplacement (constante). Le déplacement est un nombre 16-bits signés. La syntaxe est "disp(Ax)". ATTENTION : cela peut poser de réels problèmes que le déplacement soit 16-bits, en particulier si vous choisissez d'utiliser le déplacement pour adresser la base d'un tableau et le registre d'adresse (base) pour indexer ses éléments, car ce tableau devra avoir une base comprise dans la fourchette 0xFF8000 - 0x007FFF en passant par 0. Il faudra donc éventuellement chercher autre chose...

Exemple :
	org	$0
	LEA	$0,A0
			* Initialise A0 avec la valeur de l'adresse
			* effective de l'opérande source "$0",
			* c'est-à-dire $0. Attention, c'est LEA...
			* La première opérande est donc une adresse
			* effective, pas une valeur immédiate, donc
			* il ne faut pas mettre de dièse '#'.
	MOVE.L	#($F * 4),D0
			* Initialise D0 avec la valeur immédiate
			* "#($F * 4)", qui vaut $3C (60).
l1	MOVE.L	D0,tab(A0)
			* Copie D0 en mémoire à l'adresse représentée par
			* l'opérande destination, qui est une opérande
			* mémoire, dont la valeur de l'adresse effective
			* est la somme du contenu de A0 et du déplacement
			* qui est "tab". Or tab est un label (symbole), donc
			* sa valeur est l'adresse de l'endroit où il est
			* défini, c'est-à-dire $100, donc la valeur de
			* adresse effective "tab(A0)" est $100 + A0.
			* Au premier tour de la boucle, A0 vaut $3C donc
			* "tab(A0)" vaut $13C.
	SUBA.L	#4,A0
			* Retranche $4 au contenu de A0. Au premier tour de la
			* boucle, A0 vaut $3C, donc après la première
			* exécution de cette instruction, A0 vaudra $38. Et
			* aux tours suivants, ainsi de suite : $34, $30,
			* $2C...
	DBRA	D0,l1
			* en Pseudo-C :
			* --D0;
			* if (D0 != -1) goto l1;
			* A chaque tour de boucle, D0 est décrémenté et tant
			* que D0 est différent de -1, on refait un tour en
			* sautant à l'adresse correspondant au label l1.
			* ATTENTION : c'est D0.W qui est
			* utilisé, autrement dit les 16-bits de poids faible
			* uniquement. Par conséquent, -1 signifie ici $FFFF
			* (65535 ou -1 en 16-bits signés) et pas $FFFFFFFF
			* (4294967295 ou -1 en 32-bits signés).
	org	$100
tab	ds.l	$10
			* Déclaration d'une zone de $10 (16) double-mots
			* (32-bits). Sa valeur est inconnue avant
			* initialisation.

Le coin de Lea :

Voici donc comment réagit notre chère Léa, euh... LEA !

Exemples :

Adressage indirect par registres de base et d'index

Le mode d'adressage indirect par registres de base et index permet d'accéder à une donnée en calculant son adresse effective à partir de la somme entre le registre de base et le registre d'index. Le registre de base est un registre d'adresse 32-bits "Ax", alors que le registre d'index est un registre quelconque "Ri" (données "Di" ou adresse "Ai") 16-bits signés ou 32-bits. Il faudra donc utiliser les suffixes .L (par défaut) ou .W pour sélectionner le registre "Ri" 16-bits signés ("Ri.W") ou 32-bits ("Ri.L"). La syntaxe d'une telle opérande est la suivante : "(Ax,Ri.s)" avec 's' qui est soit ".L", soit ".W". Le registre de base et le registre d'index peuvent être les mêmes. Rappel : "Ri" est juste un racourci pour ne pas avoir à dire "Di ou Ai" constamment dans mes explications, alors bien évidemment n'écrivez jamais d'opérande avec un registre qui s'appelerait R0 par exemple...

Exemple :
	org	$0
	LEA	tab,A0
			* Initialise A0 avec la valeur du symbole "tab", qui
			* est un label. A0 vaut $100 après l'exécution de cette
			* instruction. Attention, c'est LEA...
			* La première opérande est donc une adresse
			* effective, pas une valeur immédiate, donc
			* il ne faut pas mettre de dièse '#'.
	MOVE.L	#$8,D0
			* Initialise D0 avec la valeur immédiate
			* "#$8". D0 vaut donc $8 après l'exécution de cette
			* instruction.
	MOVE.L	(A0,D0.L),D1
			* Initialise D1 avec la donnée dont l'adresse est
			* l'adresse effective de l'opérande mémoire, qui
			* est l'opérande source. Sa valeur est la somme des
			* deux registres A0 et D0.L, donc vaut
			* $100 + $8 = $108. Par conséquent, D1 vaut 7 après
			* l'exécution de cette instruction.
	org	$100
tab	dc.l	3	* L'adresse de cette élément est $100 ;
	dc.l	5	* $104 ;
	dc.l	7	* $108 ;
	dc.l	11	* $10C.

Le coin de Lea :

- Salut ! Nous revoilà avec Léa, euh... LE...
- Hey là ! Salut !
- Hein ?! [Léa, elle parle ??]

Exemples :

Adressage indirect par registres de base et d'index et par déplacement

Le mode d'adressage indirect par registres de base et d'index et par déplacement permet d'accéder à une donnée en calculant son adresse effective à partir de la somme du registre de base "Ax", du registre d'index "Ri" et du déplacement, qui est une valeur 16-bits signés. La syntaxe est "disp(Ax,Ri.s)" avec s dans { ".L"; ".W" }.

Exemple :

Dans la suite je ne commenterai que les instructions nouvelles ou celles sur lesquelles je souhaite détailler un point.

	org	$0
	LEA	tab,A0
	MOVE.L	#$8,D1
	MOVE.W	$2(A0,D1.L),D0
			* Copie la donnée dont l'adresse effective est la
			* valeur de "$2(A0,D1.L)", c'est-à-dire
			* $2 + $100 + $8 = $10A dans D0.W. D0.W vaut donc 17
			* après l'exécution de cette instruction.
	org	$100
tab	dc.w	3	* [0] [0] $100 (Element 0 champ 0 adresse $100)
	dc.w	5	* [0] [1] $102 (Element 0 champ 1 adresse $102)
	dc.w	7	* [1] [0] $104
	dc.w	11	* [1] [1] $106
	dc.w	13	* [2] [0] $108
	dc.w	17	* [2] [1] $10A

Le coin de Léa :

- J'y crois pas... elle parle ! Je dois devenir fou... Mais ! C'est quoi cette fantaisie dans le titre ?!
- Je me suis dit qu'une petite touche de féminité ferait...
- Hein ?! Mais, ça va pas non ?!... Mon beau titre, snif... T'es pas folle toi !!
- C'est pas moi qui parle à une instruction en assembleur...
- [goutte]

Exemples :

Adressage indirect par PC et par déplacement relatif

Le mode d'adressage indirect par PC et par déplacement relatif permet d'accéder à une donnée en calculant son adresse effective en sommant la valeur de PC (le registre qui contient l'adresse de l'instruction en train d'être exécutée (et même l'adresse du morceaux en train d'être exécuté)) et le déplacement relatif (la différence entre l'adresse de la donnée à adresser et l'adresse de l'instruction courante (l'encodage du déplacement relatif en fait)). Fort heureusement, ce n'est pas à vous de vous amuser à calculer cette différence, l'assembleur le fera pour vous. Tout ce que vous avez à faire, c'est de donner l'adresse de la donnée à accéder comme si de rien n'était. La syntaxe est "disp(PC)" où "disp" est le déplacement absolu (comme pour les adressages vus précédemment). L'avantage de cet adressage est que le code de cette instruction ne dépend pas de l'adresse à laquelle il est implémenté tant que la donnée recherchée reste toujours à la même distance (relative) de l'instruction. C'est particulièrement intéressant pour les bibliothèques... Notez que c'est loin d'être la seule fonctionnalité du CPU à utiliser des déplacements relatifs, en particulier beaucoup de branchements sont encodés avec un déplacement relatif, alors que bien-sûr vous n'avez jamais que le déplacement absolu à écrire ; c'est l'assembleur qui fait la conversion.

	org	$0
	MOVE.L	$100(PC),D0
			* Initialise D0 avec la donnée se situant à
			* l'adresse effective calculée à partir de
			* "$100(PC)", c'est à dire tout simplement
			* $100 !! En fait, ce n'est pas $100 qui est
			* encodé dans l'instruction en langage machine, mais
			* la différence entre $100 et l'adresse de l'encodage
			* de la distance relative (quelque chose comme $4 -
			* je ne suis pas très sûr), soit quelque chose comme
			* $FC (mais, je ne suis pas sûr). Ensuite, le CPU
			* réalise la somme entre la précédente différence,
			* qui est le fameux déplacement relatif, et
			* le registre PC, ce qui permet de retrouver $100.
			* D0 vaut donc $80 une fois l'instruction exécutée.
	org	$100
	dc.l	$80

Le coin de Léa :

(...)
- Bon, d'accord ! T'as gagné ! On garde ce titre ! Mais euh... on touche plus à rien maintenant, hein ?
- Oui, oui... [C'est encore trop fade à mon goût... il faut que je trouve quelque chose à faire...]

Exemples :

Adressage indirect par PC, par index et par déplacement relatif

Le mode d'adressage indirect par PC, par index et par déplacement relatif permet d'accéder à une donnée en calculant son adresse effective à partir de la somme entre PC, le registre d'index "Ri.s" avec s dans { ".W"; ".L" } et le déplacement relatif (cf. Adressage indirect par PC et par déplacement relatif). La syntaxe est "disp(PC,Ri.s)" avec "disp" le déplacement absolu et pas relatif.

Exemple :
	org	$0
	LEA	$FC,A0
	MOVE.L	$4(PC,A0.W),D0
			* Initialise D0 avec la donnée se situant à
			* l'adresse effective calculée à partir de
			* "$4(PC,A0.W)", c'est à dire la somme entre $4 et
			* le contenu signé de A0.W, c'est-à-dire $FC, donc
			* $100 (Faites comme si PC n'existait pas).
			* D0 vaut donc $80 une fois l'instruction exécutée.
	org	$100
	dc.l	$80

Hein !! Mais, qu'est-ce que ?!

Le coin de Léa :

- MAIS, QU'EST-CE QUI SE PASSE ICI ?! ET POURQUOI MES REPLIQUES SONT ECRITES AVEC UNE COULEUR NULLE ??
- Hmm... pardon... tu disais quelque-chose ?
- Grrr... bon, d'accord !! T'as gagné ! Finallement, c'est pas si moche comme couleurs...
- Bien, tu t'améliores !
- Hum !!... Oh !!... Merci, pour la couleur...
- Je suis triste...
- Pourquoi ?
- C'est mon dernier "Le coin de Léa". Je ne sais pas faire d'autres adressages après ce dernier. Snif...
- Allez, ce n'est qu'un au revoir. Tu reverras des gens dans plein de programmes !! Ganbatte !! Daijoubu ?
- Hai ! Arigatou !! Mata ne !!

  • 	LEA	$FFFC,A0
    	LEA	$104(PC,A0.W),A1
    			* Initialise A0 avec l'adresse effective de
    			* l'opérande source, c'est-à-dire
    			* $104 + $FFFC (16-bits signé donc -$4) = $100
    			* A1 vaut donc $100 après l'exécution de cette
    			* instruction. L'instruction est toujours 32-bits.
    
  • 	org	$0
    	LEA	$FFFC,A0
    	MOVEA.L	$104(PC,A0.W),A1
    			* Initialise A0 avec la donnée dont l'adresse est
    			* l'adresse effective de l'opérande source, donc la
    			* $100. Par conséquent A1 vaut $80 après l'exécution de
    			* l'instruction.
    	org	$100
    	dc.l	$80
    

Adressages indirects par registre (base) avec pré-décrémentation ou post-incrémentation

Les modes d'adressage indirects par registre avec pré-décrémentation ou post-incrémentation permettent d'adresser une donnée comme avec l'adressage indirect avec registre (base). La différence réside dans le fait que le registre en question va être soit pré-décrémenté ou post-incrémenté, c'est-à-dire respectivement décrémenté avant ou incrémenté après son utilisation dans le calcul de l'adresse effective. Attention : la valeur de la décrémentation ou de l'incrémentation dépend de la taille des données manipulées : 1 si .B, 2 si .W ou 4 si .L. La syntaxe est "-(Ax)" pour la pré-décrémentation et "(Ax)+" pour la post-décrémentation. Notez que ce comportement et la syntaxe sont similaires à ceux de la décrémentation et de l'incrémentation en C : la valeur de la variable décrémentée ou incrémentée dans l'expression dans laquelle est écrite dépend la position du "--" ou du "++" par rapport à la variable (avant ou après). Ce mode d'adressage est roi dans l'art de la manipulation de chaînes (de caractères ou octets (byte/8-bits), mais aussi de mot (word/16-bits) ou même de double-mots ou mots longs (long/double word/32-bits)). Illustrons donc ceci avec une copie de chaîne "mirroir" !

Exemple :
	org	$0
	LEA	s1,A0
	LEA	s2end,A1
	MOVE.L	#s1len,D0
			* Combien y-a-t-il de caractères à copier ?
l1	MOVE.L	(A0)+,-(A1)
	DBRA	D0,l1
	TRAP	#15	* Fin du programme avec le simulateur.

	org	$100
s1	dc.b	'Hello World!'
s1end
s1len	equ	s1end - s1
	even		* Aligne l'adresse courante sur l'adresse
			* paire suivante.
s2len	ds.b	s1len
s2end
	end

Récapitulatif de la syntaxe des différents adressages

Légende :
disp (déplacement absolu) :
Nombre 32-bits ou 16-bits signé.
s (size) 
Remplacez "s" par ".W" ou par ".L", suivant la partie du registre, auquel vous accollez ce suffixe, que vous voulez accéder.

Récapitulatif des adressages
Déplacement disp
Indirect par registre de base (Ax)
Indirect par registre de base et déplacement disp(Ax)
Indirect par registres de base et d'index (Ax,Ri.s)
Indirect par registres de base et d'index, et déplacement disp(Ax,Ri.s)
Indirect par PC et déplacement relatif disp(PC)
Indirect par PC et registre d'index, et déplacement relatif disp(PC,Ri.s)
Indirect par registre de base avec pré-décrémentation -(Ax)
Indirect par registre de base avec post-incrémentation (Ax)+

4. Les instructions de gestion de la pile d'exécution

La pile d'exécution utilise le registre A7. Voici les instructions pour la gérer : ("s" peut valoir ".W" ou ".L", tandis que "S" peut valoir ".B", ".W" ou ".L")

	MOVE.s	Ri,-(A7)
			* Empile la valeur du registre Ri. Il est préférable
			* que A7 reste pair, sinon si A7 est impair et qu'on
			* empile quelque chose d'autre qu'un octet...
			* plantage.
	MOVE.s	(A7)+,Ri
			* Dépile dans le registre Ri. Il est préférable que
			* A7 reste pair, sinon... re-risque-de-plantage...
	LINK	Ax,I
			* Empile Ax, initialise Ax à la valeur actuelle de
			* A7, puis retranche I à A7. Cette instruction sert
			* à se créer un stack frame. On utilisera
			* traditionnellement cette instruction au début
			* d'une routine.
	UNLK	Ax
			* Initialise A7 à la valeur actuelle de Ax, puis
			* dépile dans Ax. Cette sert instruction sert à
			* annuler l'action de LINK. On utilisera
			* traditionnellement cette instruction en fin d'une
			* routine avant le retour à l'appelant.