ASM680X0 - Chapitre 2
Un article de GuruMed.
La représentation des nombres en informatique.
La révolution n'est pas un dîner de gala, camarade.
Avant de nous attaquer à la description des instructions qui opérent des calculs mathématiques sur les registres, il est INCONTOURNABLE d'avoir une idée PRECISE de la façon ceux-ci représentent leurs valeurs, et ce qui se passe quand on réalise un calcul dessus. Souvent les débutants en asm restent bloqués parcequ'ils n'ont pas compris le fonctionnement exact d'une opération. De plus, en assembleur, on manipule des registres ou des zones mémoires en tant que champs de bits, leurs types étant totalement implicite (valeurs entiere, signé ou pas, valeurs réelles flottantes, textes,...), contrairement aux autres langages ou on sait toujours le type exact d'une variable. Pourtant, Vous verrez que quiconque sait effectuer une addition, une soustraction, une multiplication , une division et une opération logique en sait assez pour comprendre ce qui suit.
Vous devez aussi avoir en tête ce vocabulaire mathématique:
- nombre entier: nombre qui n'a pas de virgule.
- nombre réel: nombre qui peut avoir des chiffres aprés la virgule.
- nombre naturel: on dira non-signé: qui est forcément positif.
- nombre relatif: peut etre positif OU négatif.
Nous avons vu que le 680x0 posséde 8 registres de données parfait pour réaliser des calculs, noté de d0 à d7. Chacun contient (est une suite de ...) 32 bits (ou 4 octets).
Représentation d'un registre 32bits sous 680X0
On voit que par convention décidé par le constructeur motorola à l'époque, les bits sont notés de 0 à 31 et sont dessinés de droite à gauche, du bit 31 (dit bit de poid le plus fort) vers le bit 0 (bit dit de poid le plus faible, on verra plus tard pourquoi.)
Quand on fera du 680x0, on pourra désigner tel ou tel bit dans un registre par son numéro de 0 à 31. On ne peut donc pas se tromper. par exemple, l'octet de poid faible désigne les bits 0 à 7 inclus. Certaines instructions assembleurs utilisent ces numéros pour désigner un bit donné (comme btst, qui teste un bit.) Notez toutefois que c'est la convention 680X0, par exemple en powerpc, on note les bits dans l'autre sens (bit 0 poid fort ).
Représentation de nombres entiers positifs
c'est pas compliqué ! Bascule mec !
Un bit est une bascule. Cela signifie qu'il peut prendre 2 formes noté 0 ou 1. Génial. Mettons-en 2 côte à côte. Chacun peut designer 0 ou 1, ça fait quatre expressions possibles:
00 01 10 11
On peut vite en conclure que si on a un champ de 'n' bits, on peut définir (2 puissance n) façons de placer ces bits à 0 ou à 1.
Hé bien figurez-vous qu'en comptant en 'Base 2', on peut lire dans un champ de 'n' bits, les valeurs entiéres positives allant de zéro à ((2 puissance n)-1). (pour 2 bit, on peut exprimer de 0 à 3)
Qu'est ce qu'une Base ? je vous renvoie à votre programme de cp. Par exemple dans la vie courante c'est plutôt la base 10 qui est utilisée (décimal), et qui définie 10 chiffres possibles de 0 à 9 pour un 'digit' (chiffre exprimé): le principe de compter avec une base c'est que quand ça dépasse, oulàlà, il faut une autre colonne: à droite. Exemple en décimal:
9+1 = 10 99+1 = 100
dans chaque nouvelle colonne à droite, le chiffre est à multiplier par (10 puissance le numero de la colonne, la premiére colonne à gauche étant numéroté zéro.), puis on additionne le tout pour avoir la valeur. C'est pareil en binaire (binaire = base 2), sauf que les chiffres sont à multiplier par (2 puissance le numero de la colonne).
On peut donc dire qu'en binaire, le bit 0 'vaut' 1, le bit 1 vaut 2, le bit 2 vaut 4,... Et voilà. Vous devez déjà être en mesure de faire des conversions d'expressions binaires vers une expression décimale.
Pour des conversions d'expression décimale vers binaire, la méthode est simple: On part des valeurs des plus gros bit binaire à droite, on regarde si notre nombre décimal à convertir est plus grand, si oui, on soustrait la valeur de ce bit de notre nombre, et on allume ce bit, et on recommence avec le prochain bit de poid faible à gauche avec le reste de cette soustraction: par exemple, 146 est compris entre 0 et 255 donc 8 bits suffisent à l'exprimer. le bit de poid fort vaut 128. donc:
- 146 -128 = 18
- ensuite on a le bit qui vaut 64. 18 est plus petit: on passe.
- ensuite on a le bit qui vaut 32. 18 est plus petit: on passe.
- ensuite on a le bit qui vaut 16:
- 18-16 = 2
au final on obtient:
- 146 = 128+16+2
l'expression binaire de 146 sur 8 bit est donc:
- % 10010010
valeurs des 16 premiers bits (utile pour des conversions)
| numéro du bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| valeur du bit | 32768 | 16384 | 8192 | 4096 | 2048 | 1024 | 512 | 256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Expressions de mêmes nombres sur 8 bits non signés.
| Binaire | Décimal | HéxaDécimal |
|---|---|---|
| %00000101 | 5 | $05 |
| %10000000 | 128 | $80 |
| %10010010 | 146 | $92 |
| %00001111 | 15 | $0F |
| %00001001 | 9 | $09 |
| %11111111 | 255 | $FF |
| %10011010 | 154 | $9A |
| %00000011 | 3 | $03 |
Y-a-t-il d'autres façon de coder des entiers positifs ?
vive la diversité. (chapitre facultatif.)
Nous en sommes seulement à définir une expression pour des nombres entiers positifs, tout devrait être simple, mais il faut noter que D'AUTRES CODAGES SONT POSSIBLES POUR DES ENTIERS POSITIFS, dont le 'décimal codé binaire' (BCD in english), je m'étendrais pas, ça consiste à prendre les bits 4 par 4: on peut alors définir 16 valeurs avec chaques, mais voilà, on dit qu'on se sert que des 10 premiéres valeurs possibles (0 à 9), et donc les valeurs 10 à 15 sont impossibles. A quoi ça sert ? à avoir un champ de bit qui explicite des nombres décimaux, en base 10 au lieu d'etre en base 2. Donc avec 32 bits, on a 8 nombres possibles en base 10, ce qui fait 100 000 000 nombres différents. Cette représentation est utilisé par des vieux processeurs dans les calculatrices. Pourquoi cela nous concerne -t-il ? parce que jusqu'au 68030 nous avons des instructions telles que ABCD qui travailles en BCD !!! (ABCD= addition BCD). mais tout ceci disparait dés le 68040 et de toutes façon ça rame et ça craint.
- OUBLIEZ CE CHAPITRE, LE BCD CRAINT ET N'EXISTE PAS -
Quelques trucs pour bien maîtriser son domaine
Quand vous aurez à choisir un type de donnée pour une variable, il faudra bien réfléchir à bien définir le nombre d'octet nécessaire en fonction de vos besoins. Vous ne pourrez pas exprimer '256' avec 1 octet, ça s'arréte à 255. En assembleur, 255+1 en 8 bit donne 0, plus une information comme quoi il y a dépassement sur le dernier calcul. Et ainsi de suite, sur 16 bit, en non-signé vous pouvez exprimer de 0 à 65535, pas au delà. Il faut donc savoir exactement, sur un nombre de bits donnée, 'le domaine de variation' des valeurs possibles. 32 bits peut représenter les valeurs de zero à '4Giga -1' je peux vous le dire du tac au tac. Comment ? En informatique, on parle de kilo qui represente '2 puissance 10=1024' (et en aucun cas 1000). De méme, Mega représente '2 puissance 20' et giga '2 puissance 30'. Si vous connaissez cette régle mathématique (^ signifie puissance):
(2 ^n) x (2 ^m) = 2 ^(n+m)
Vous devinez que 2^32 = (2^30) x (2^2)
soit 1 giga multiplié par 4, soit 4 giga.
exercice: Quelle est la valeur entiére maximum exprimable avec 24 bits ?
Facile 2^24 = 2^4 x 2^20 = 16 mega octet
Donc la réponse est: '16 megaoctet moins un.'
Si vous connaissez votre table de puissance de 2, multiplier une puissance de 2 avec une autre est aussi trés facile pour les mêmes raisons: 64 x 512 semble difficile à réaliser de tête, mais:
2^6 x 2^9 = 2^(6+9) = 2^15 = 32768 ... rapide !
Et vous voilà devenu le nouveau hans rudiguer !
Comment le 680X0 voit-il les nombres entiers RELATIFS
La MAGIE du codage 'complément à 2'
En informatique il existe 2 moyens de définir des entiers négatifs. le premier ne nous interesse pas en 680X0 ou peu et à pour nom 'valeur absolue plus signe': ça consiste à utiliser le bit de poid le plus fort pour indiquer si le nombre est négatif, et le reste des bits pour exprimer le valeur entiere a multiplier par -1 ou pas. Dans ce codage, pour n bit, on aurait le domaine de variation suivant:
de: -(2^(n-1) -1) jusqu'a : +(2^(n-1) -1)
(sur 4 bit, on aurait de -7 à 7)
Il y a énormément de désavantage à utiliser ce codage, le moindre étant qu'on peut exprimer 'moins zero' et 'plus zero', ce qui est stupide.
Non, tout les processeurs sérieux travaillent avec des entiers relatifs en 'COMPLEMENT A 2': De quoi s'agit-il ?
D'abord, souvenez vous de vos cours primaire, ou on vous faisait faire des soustractions à la main, colonnes par colonnes, avec des retenues à reporter sur la colonnes suivantes. en binaire c'est pareil.
Quand on opére une soustraction de 1 sur le nombre 0 en binaire,
on obtient tout les bits à 1, et le bit spécial qui averti d'un depassement s'allume (il se trouve dans un registre spécial):
%00000000 - %00000001 = %11111111
Lu en temps que non-signé, tout les bits à 1 représentent le plus grand nombre possible (255,le compteur à tourné, mais à l'envers). Et bien, en tant que signé 'complement à 2', cela veut dire: '-1'.
si on refait moins un sur moins un:
%11111111 - %00000001 = %11111110
%11111110 : exprime -2 en signé, ou 254 en non-signé.
On l'aura compris, en 'complément à 2', un nombre négatif commence par un ou plusieurs '1'. Voici le nombre signé le plus petit sur 8 bit: -128 = %10000000
Et voici le plus grand: 127: %01111111
En 'complément à 2', sur n bits, le domaine de variation est donc:
de: -(2^(n-1)) jusqu'a : +(2^(n-1) -1)
(sur 4 bit, on aurait de -8 à 7)
On a vu que le type d'un champ de bit (registre, memoire) n'était pas défini en asm, donc comment va-ton définir qu'un registre représente un signé ou un non-signé ? Par les instructions que l'on va utiliser avec:
par exemple, les instructions de multiplications et de divisions ont des versions signées et non signées(Muls,Divs sont signés, Mulu,Divu sont non-signés).
L'énorme avantage du complément à 2, c'est que les additions et soustractions sont les mêmes pour du signé ou du non-signé. Simplement, la notion de dépassement (overflow) n'intervient pas au même 'endroit'.
Note: l'instruction 'neg' réalise une multiplication par -1 en complément à 2 rapide:
neg.w d0
Un truc: Comment le faire sur papier sans s'embéter ? il suffit d'inverser tout les bits et de faire +1, ça marche dans tout les cas. Voici donc un equivalent un peu plus lent de 'neg.w d0':
not.w d0
add.w #1,d0
derniére note: remarquez que dans une représentation 'valeur absolue + signe' comme en 'complément à 2', tester le bit de poid fort indique si le nombre est negatif ou positif. (pour tester cela, une instruction existe de toute façon: tst.)
Changement de Format d'un entier relatif.
Complément à 2, la suite.
Nous savons maintenant que le type d'un registre en assembleur est implicite, et dépend de ce que l'on en fait avec les instructions: Le format ( .b, .w, .l) permet de définir 8,16 ou 32 bits, et l'usage d'instructions signé ou pas définissent sa nature.
Au cours d'un programme, il arrive que parfois on ait besoin de changer le format d'un nombre,signé ou pas. Par exemple, additionner un nombre exprimé sur 8 bit signé dans la mémoire, sur un nombre exprimé en 16 bit signé dans d0.
move.b (a0),d1 ; ceci lit un octet à l'adresse a0 et le met dans d1
add.b d1,d0 ; FAUX.
premiére erreur possible: la bonne valeur est dans d1, mais
l'addition est en .b alors qu'on à dit que la valeur de d0 est sur 16 bits (.w): l'addition risque d'être fausse.
move.b (a0),d1 ; ceci lit un octet à l'adresse a0.
add.w d1,d0 ; ceci est archi-faux. bug.
Qu'est ce qui ne va pas ? move.b a bien rempli son rôle en remplissant les 8 bit de poids faible de d1, mais n'a pas touché les 24 autres bits de poid fort, qui peuvent contenir n'importe quoi.
Quand bien même, add.w n'utilise que 16 bits de poids faibles, donc une instruction clr.w (clear) peut mettre à 0 ces 16 premiers bits avant le move.b:
clr.w d1 ; efface les 16 premiers bits de d1.
move.b (a0),d1 ; rempli les 8 premiers bits de d1.
add.w d1,d0 ; toujours FAUX.
On est sur que d1 est constitué des bons 8 premiers bits et que les 8 suivant sont vides. Ceci marcherait si on avait dit que les 8 bits en mémoire étaient non-signé. Mais on a dit qu'ils étaient signé.
Voici la solution:
move.b (a0),d1 ; charge donné signé en 8 bit
ext.w d1 ; extension signé de 8bit vers 16 bit.
add.w d1,d0 ; addition 16 bit.
Pour passer une valeur signé en complément à 2 vers un format plus grand, une instruction spéciale est nécessaire. ext.b va remplir correctement nos 8 bits de poid fort, soit avec des 1, soit avec des 0, selon que l'expression '8bit' est negative ou positive, pour que le sens 'signé' soit gardé. Ces autres formes d'extensions sont possibles:
ext.w dx ; extension signé de 8bit vers 16 bit. (.b->.w)
ext.l dx ; extension signé de 16bit vers 32 bit. (.w->.l)
- a partir 68020
extb.l dx ; extension signé de 8bit vers 32 bit. (.b->.l)
Pour faire des conversions de nombres signés vers un format plus petit, il n'y a pas d'instruction à utiliser, mais il peut y avoir des dépassements, puisqu'on 'coupe' des bits de poids fort.
De la représentation héxaDécimale et de son interêt
Vous connaissez la représentation décimale, nous avons vu la représentation binaire ou les bits sont directement visibles: En assembleur 680X0 on les notes précédé d'un '%'.
exemple:
move.b #%01010101,d0
Familiarisons nous maintenant avec l'hexadécimal: en assembleur 680X0, un chiffre exprimé en hexa se précéde d'un '$'. cette écriture est équivalente à la précédente:
move.b #$55,d0
Il s'agit en fait d'écrire en base 16, les chiffres possibles étant:
0 1 2 3 4 5 6 7 8 9 a b c d e f (donc avec f=15)
Donc $0f signifie 15, et $10 signifie 16.
L'hexadécimal est souvent utilisé quand on debugue par exemple, pour donner le contenu d'un registre, ou pour montrer une adresse mémoire. seulement 8 chiffres sont necessaire pour exprimer 32 bits, chaque chiffres exprimant exactement 4 bits:
move.l #$84fa8bc5,d0 ; rempli 32 bits.
move.l #$00010000,d0 ; 65536 = 64 kilo.
move.l #$0000ffff,d0 ; 65535 = 64 kilo -1.
le décimal permet de se faire une idée de l'ordre de grandeur,mais pas de la position des bits.
le binaire permet de se faire une idée de l'ordre des bits,mais pas de l'ordre de grandeur.
L'avantage de la notation hexadécimale, c'est qu'elle permet de se faire rapidement une idée de l'ordre de grandeur ET de la position des bits. N'oubliez pas aussi qu'il s'agit de 3 notations qui désignent la même chose. Je ne veux pas entendre de questions du genre: 'je dois faire un move.l en decimal ou en hexa ?'
Les Instructions de multiplications
Il existe 2 instructions pour la multiplication: mulu pour multiplier 2 valeurs non signé (u pour unsigned,ne pouvant prendre une valeur negative), et muls pour la multiplication signé (avec résultat signé donc.)
muls.w d0,d1 ; d1 = d0*d1
muls.w memoire,d1 ; d1 = mémoire*d1 (cette forme d'adressage est possible)
muls.w (a0),d1 ; celle là aussi (.w indiqué par a0) ;)
muls.w #valeur,d1 ; cette forme immediate aussi.
Comme d'habitude en 680x0, c'est le registre de gauche qui reçoit le résultat.
Maintenant la chose importante à retenir: muls.w réalise la multiplication de 2 expression 16 bit, et MET LE RESULTAT sur 32bit.
donc les 32 bits de d1 sont modifié par muls.w d0,d1.
Pourquoi ? parce que, de part le mécanisme de la multiplication, (et quelque soit la base utilisé), si vous multipliez un nombre de n chiffres avec un nombre de m chiffres, il faut prévoir (n+m) chiffres pour exprimer le résultat. (la crise mystiiique!!!)
donc pour mulu/s.w : 16+16 =32. Pour ces raisons, muls.w ne peut pas créer de dépassement :).
NOTE IMPORTANTE
si on écrit dans un source:
muls d0,d1
sans préciser de format de taille (.b,.w,.l), le format .w est toujours utilisé a defaut. c'est en fait vrai avec toutes les instructions 680x0 par convention, .w (16bit) est à défaut. je conseille FORTEMENT de toujours spécifier ce format: certains assembleurs (devpac) peuvent forcer le format à defaut en '.l'.
De plus, muls.l n'apparait que depuis le 68020.
FIN DE LA NOTE IMPORTANTE
muls.l d0,d1
mulu.l d0,d1
on l'aura compris, ces instruction réalisent une multiplication de 32 bit x 32 bit vers un résultat... en 32 bit dans d1. Il peut donc y avoir ou pas dépassement. (il faudrait 64bit pour exprimer le résultat.) Malheureusement, ah làlà le 68020 n'est pas un processeur 64 bit. Si ? ah bon:
on a la forme:
muls.l source,resultatFort:resultatFaible
exemple:
muls.l d0,d1:d2
Depuis le 68020, cette forme réalise (d0 x d1), mais le résultat est posé dans 64 bits, en utilisant 2 registres 680x0 ! (notez que meme un PPC implémenté comme dans nos amiga ne peut pas le faire !)
(note: on m'a dit que sur 68060, cette forme était en fait émulée.)
Mauvaise nouvelle: jusqu'au 68040, les multiplications sont trés lentes comparés a une addition ou un move, qui ne mettent que de 2 a 4 cycles, un muls mettra 24 ou 40 cycles. comme l'interêt de l'assembleur est d'optimiser, quelques trucs existes. (note: en 68060 et en PPC, une multiplication prend le meme temps qu'une addition.)
premiere note de bon sens:
add.w d0,d0 ; est l'equivalent optimisé de:
muls.w #2,d0 ; ...sauf que ici les 16 bits poid fort sont effacé
De plus:
move.w d0,d1
add.w d0,d0
add.w d1,d0 ; est equivalent mais plus rapide que:
muls.w #3,d0 ; ...sauf que ici les 16 bits poid fort sont effacé
Une autre technique consiste à pré-calculer des tables de résultat de multiplication, puis d'indexer dessus pour trouver sa valeur.
c'est en effet plus rapide, mais il faut alors faire des choix entre la taille memoire de la table et la précision en nombre de bit exprimé par la multiplication.
Les Instructions de décalages de bits
et les réels virgule-fixe.
Il est possible de réaliser des 'décalages' sur les registres de données, cela signifie changer la valeur des bits comme si ils glissaient tous à droite ou à gauche, en spécifiant le nombre de bit
de décalages. cela peut être joli dit comme ça, on pourrait imaginer un scrolling (bien lent) utilisant ces instructions. L'interêt est plus mathématique: comme on l'a vu plus haut, chaque bit 'vers la gauche' vaut 2 fois plus que le bit qui est à sa droite. (ouai ! j'ai réussi à exposer des idées politique sur gurumed.) Donc si vous avez compris, décaler un registre de 1 bit vers la gauche multiplie sa valeur de (2 puissance le nombre de bit de décalage gauche), et décaler un registre vers la droite le divise de (2 puissance le nombre de bit de décalage droite.)
Premiere application de la chose: vous pouvez considérer qu'un décalage prend environ de 4 à 6 cycles, beaucoup plus rapide d'une multiplication avec mulu/muls ,et infiniment plus rapide qu'une division (divu/divs).
muls.l #64,d1 ; est aisément remplacé par:
lsl.l #6,d1 ; logical shift left (decalage gauche de 6 bits).
et c'est carrément de 4 à 8 fois plus rapide ! (sauf 060 ou les multiplications sont aussi rapides que des additions.)
jusqu'au 68040 inclus, pour multiplier par 320 par exemple, ceci est plus rapide qu'un muls.l #320,d0:
move.l d0,d1
lsl.l #8,d0 ; *256
lsl.l #6,d1 ; * 64
add.l d1,d0 ; * 320.
cela dit, 'add.l d0,d0' est plus rapide mais équivalent à 'lsl.l #1,d0'. Note importante: le nombre de bit de décalage maximum est 8.
si vous voulez décaler de 10 bits, il faudra utiliser 2 instructions: decaler de 8, puis de 2.
Voyons les instructions disponibles pour réaliser ces décalages:
à noter que shift signifie décalage, left signifie gauche, right signifie droite.
lsl(.b,.w,.l) #nbbit,dx ; logic shift left (multiplie)
lsr(.b,.w,.l) #nbbit,dx ; logic shift right(divise)
asl(.b,.w,.l) #nbbit,dx ; arithmetic shift left (multiplie SIGNEE)
asr(.b,.w,.l) #nbbit,dx ; arithmetic shift right(divise SIGNEE )
rol(.b,.w,.l) #nbbit,dx ; rotation left (un peu + lent)
ror(.b,.w,.l) #nbbit,dx ; rotation right. (un peu + lent)
swap dx ; echange les 16 bits forts avec les 16 bits faibles.
Notez qu'il existe une version signée (asr) et une version non signée (lsr) de décalage a droite. (asr garde le signe en mettant des 0 ou des 1 dans les nouveaux bits apparaissant à gauche.)
A gauche, je n'ai jamais compris d'ailleurs pourquoi il existait 2 instructions (asl et lsl) puisque leurs comportements est le même. Un truc con: les instructions finissant par 'l' muLtipLient, et celles qui finissent par r Réduisent.
rol et ror sont des versions qui font apparaitre les bits qui 'dépassent' de 'l'autre coté' du registre, mais sont un peu plus lentes.
'swap dx' est par contre trés interessant. les 16 bits de poid fort et de poid faible sont échangés, et c'est une des instructions les plus rapide d'éxecution. (avec move et add= 1 ou 2 cycles.)
si vous imaginez travailler avec quelques valeur ".w", vous pouvez en 'cacher' d'autres dans les poids fort de registre déjà utilisé, et y faire appel entre 2 swap. (on a toujours besoin d'utiliser les registres au maximum.). en .w, un simple swap suivi d'un clr.w multiplie la valeur en .l par (1 puissance 16)=65536 , en signé ou en non-signé. un autre swap la redivisera par le même nombre.
bfins, bfext...
ces instructions de décalages compliqué mais parfois utiles, dispo depuis le 68020, ne seront pas expliqués ici. (Il s'agit de déplacer une suite de bit d'un registe vers un autre, en décalant ou pas.les powerpcien reconnaitrons rlwimi)
Les réels virgule fixe
Une des applications possible des décalages est de les utiliser pour 'créer' des réels 'virgules fixe', ce qui est une bonne idée d'optimisation asm: nous avons vu que les registres de données sont des entiers, donc l'unité est alors la valeur du bit de poid le plus faible: '1'. Impossible d'exprimer "2.5" ou 0.75, ou "256.125". Il existe bien parfois une unité "fpu" (floating point unit) aux 680x0, présente systématiquement à partir du 68040, et qui fourni des registres et des instructions "réels à virgules flottante.", mais nous n'en parleront pas ici. Il est possible de simuler des réels avec une précision de bit 'aprés la virgule' constante, En appliquant à nos registres entier un 'changement de repaire', en les multipliant et en les divisant par un nombre défini: par exemple 65536, ça correspond a 16bit de décalage. par exemple si en entier on réalise n = 10 / 3, on obtient 3, alors que le résultat réel est 3.33333... Si on imagine qu'on pose, en .l: 'le 16eme bit vaut 1, il est l'unité, le 17eme bit vaut 2, le 18 vaut 4,....' d'une part, et d'autre part: 'le 15eme bit vaut 0.5, le 14eme bit vaut 0.25, le 13eme bit vaut 0.125,...', il est alors possible de réaliser ce calcul:
move.l #10,d0
move.l #3,d1
swap d0 ; *65536
swap d1
divu.l d1,d0 ; prend la valeur de 3.333... * 65536
les valeurs aprés la virgules (exprimant 0.3333..) sont alors dans les 16bits faibles de d0.
Les instructions de divisions
Ca rame comme c'est pas permis.
On peut en effet atteindre les 80 cycles avec des divs.l.
n'oubliez pas d'en virer le maximum dans vos codes, cela dit quand il en faut, il en faut. Vous devez connaître par coeur le comportement étrange de cet instruction:
On a donc en .w:
divs.w d0,d1 ; divise d1 par d0,et ...
divs.w (a0),d1 ; diviseur lu en mémoire.
divs.w labelmemoire,d1
divs.w #valeur,d1 ; division immédiate possible évidemment.
divu.w d0,d1 ; version non signée (un peu plus rapide.)
divu.w (a0),d1
divu.w labelmemoire,d1
ATTENTION: en .w, le valeur 32bit de d1 est divisé par la valeur 16 bit de d0, aprés quoi le résultat de la division est placé dans les 16 bits faibles de d1 (quotient) , ET le RESTE de la division est placé dans LES 16 BITS DE POID FORT de d1.
donc lire une valeur '.l' sur un registre juste aprés une division .w est une hérésie.
divs.l d0,d1 ; depuis 68020
beaucoup plus simple: cette version divise d1 32bit par d0 32 bit, le resultat (quotient) est placé dans d1, point final.
impossible de récupérer le reste directement. sauf si:
divs.l d0,d1:d2 ; mégalent, mais 64bit.
divise la valeur 64 bit donnée par d1:d2 par le valeur 32 bit d0,
puis place le reste dans les 32bits d1 et le quotient dans les 32bits de d2.
divsl.l d0,d1:d2
pas pareil: divise seulement 32 bit(d2) par 32bits(d0), puis place reste et quotient comme pour 'divs.l dx,dy:dz'.
( les versions non signées divu existent.)
Les additions, soustractions,...
ya t-il des choses à dire la dessus ? oui.
On a vu que pour des expressions signé ou pas, les opération d'additions et de soustractions sont les mêmes. Voici des expressions possibles:
add.l d0,d1 ; add réalise une addition.
sub.l d0,d1 ; sub réalise une soustraction
add.w (a0),d1 ; + la valeur à l'adresse donné par d1.
add.w d1,(a0) ; possible aussi !
add.b memoirelabel,d1
add.w #valeurimmediate,d1
Si vous allez faire un tour du coté de la table des instructions, vous verrez qu'il existe une foule d'instruction dérivées, comme 'adda' qui n'est pas interessante:
adda.l d0,a0
adda est en fait la version spéciale pour les registres d'adressage.
il se trouve que taper 'add ...,ax' revient au même, l'assembleur remplacera le add par un adda. ATTENTION: en .w, contrairement à une addition sur un registre de donnée, un adda.w additione une valeur 16bit vers un .l (l'adda affecte toujours les 32bit d'un 'ax'.) aussi, adda n'accepte pas d'addition 8bit (.b).
Addq (add quick, rapide) a plus d'interêt:
addq.l #4,d0
addq, subq et moveq sont des formes possibles plus rapide lorsqu'on donne une valeur immédiate ( avec #), comprise entre -8 et 7 (je crois, je n'ai pas pu vérifier ici, je crois aussi que le calcul s'applique forcément en .l même si on spécifie .b ou .w (le piége !))
Une note sur le trés interessant "addx", notament utilisé pour optimiser le texture mapping:
add.l d0,d1
addx.l d2,d3 ; equivalent d'une addition 64bit: ( d2:d0 + d3:d1)
Lors d'une addition (comme ici le 1er add) le dernier bit de retenue impossible à appliquer sur un '33eme bit' est placé dans le 'bit X' du 'registre spécial invisible'. 'addx' réalise une addition classique, mais rajoute encore ce 'bit X' (donc 0 ou 1) dans l'état ou il a été laissé par la derniére instruction qui l'a modifié (ici le add). (certaines instructions touchent des bits du registre spécial ou pas, voir les instructions de tests: par exmple, move ne touche pas le bit x.)
Le résultat de ces 2 lignes est donc une addition 64bit (2 fois 32) puisque addx 'continue' le travail du 'add' précédent. De plus addx est aussi rapide qu'un add normal.
(puis mettez-ça en parallèle avec les notes sur les réels 'virgules fixes', et payez-vous une crise mystique.)
Voilàààà le chapître est fini. je pourrais aborder les instructions d'operations logiques (or, xor,and,...) mais elles sont assez évidentes dans leurs compréhension et leurs comportements.
(ah si: 'and.l d0,d1' est 2 fois plus rapide que 'and.l #valeur,d1'. )

