Connaître
le langage assembleur est une chose, savoir programmer avec en est une autre.
C’est comme quand on apprend une langue, il y a les mots mais aussi toute la
construction grammaticale qui permet de faire des phrases cohérentes. Toutefois
avant de créer un programme, on en étudie généralement l’algorithme, c’est à
dire en gros la liste des opérations effectuées par le programme. Reprenons
l’exemple du chapitre précédent :
ORG &4000
LD
HL,TEXTE ;HL pointe le début de
phrase
BOUCLE LD A,
(HL) ;A <- caractère pointé pas HL.
OR A ;A <- A OU A (détection de 0)
RET Z ;retour de programme si A = 0
CALL &BB5A ;vecteur d’affichage d’un caractère
INC HL ;HL pointe le caractère suivant
JR BOUCLE ;revient à BOUCLE
TEXTE DB "AMSTRAD CPC",0
Un
programme aussi court et efficace n’a pu être écrit sans un minimum de
raisonnement. Et ce raisonnement quel est-il ? je mémorise la phrase que
je veux afficher. Puis je me place sur la 1ère lettre. Si cette
lettre possède le code 0, c’est que la fin de la phrase est atteinte, donc je
termine le programme. Sinon j’affiche la lettre avec une routine spéciale, et
je passe à la lettre suivante. Si cette lettre possède le code 0…
Il existe
un langage algorithmique qu’on trouve souvent dans les livres de programmation.
Il est assez proche des langages évolués comme le C par exemple, bien qu’il
soit généralement universel, donc traduisible dans n’importe quel langage. Le
programme aurait pu d’abord être écrit (mais ce n’est pas obligatoire) de la
manière suivante :
Texte = "AMSTRAD CPC",0
HL
= adresse de Texte
Tant
que ((HL) ¹ 0) Faire
Afficher (HL)
HL = HL + 1
Fin
tant que
Bon dit
comme ça, c’est un peu court par rapport à l’assembleur. Mais ça a au moins le
mérite d’être parfaitement clair sur ce qu’il y a à faire. Ici on aurait très
bien pu le porter en BASIC sans trop de problèmes, mais là n’est pas la
question.
Pour vous
aidez à mieux comprendre les diverses opérations que l’on peut faire dans un
programme assembleur, je vous propose ici quelques exemples de code ou
d’algorithmes.
8-1
Opérations logiques
Il ne
s’agit pas ici de résoudre des énigmes par la logique, mais de logique binaire
(ou booléenne). Il existe 4 opérateurs logiques de base :
8-1-1 NOT (NON) : complémente les bits d’un nombre (les
inverse)
_
NOT 0 = 1
ou 0 = 1 (on dit 0 barre = 1)
_
NOT 1 = 0
ou 1 = 0 (on dit 1 barre = 0)
L’instruction CPL
complémente tous les bits du registre A par exemple. Pour n’inverser que
quelques bits, on utilisera plutôt l’opérateur XOR (OU exclusif).
ORG &4000
LD A,(NBR)
CPL
...
NBR DB %00000000
Attention : il ne faut pas confondre cette instruction
avec NEG qui change le signe d’un nombre. En effet NEG = CPL + 1
8-1-2 OR (OU)
L’opération se fait entre 2 bits. Si l’un OU l’autre des
bits, OU les 2 sont à 1, alors le résultat est 1 :
0 OU 0 = 0
0 OU 1 = 1
1 OU 0 = 1
1 OU 1 = 1
C’est l’instruction OR s ( A <- A OU s) qui se charge de cette
opération entre tous les bits du registre A et s. En pratique, elle sert à plusieurs
choses :
Forcer quelques bits à 1 :
A = %1001 0010 dont
on voudrait forcer les 4 bits de poids faible à 1
B = %0000 1111
« masque OU »
OR B
=> A = 1001 1111
Les 4 bits de poids fort (1001) sont inchangés et les 4 bits de poids
faible sont à 1
Détection du nombre 0 :
Déjà aperçue dans le programme d’exemple, cette opération
permet de savoir si un nombre est nul.
LD A,(NBR)
;A <- Nombre
OR A
;A <- A OU A
RET Z
;Retour si A = 0
Faire un OU d’un nombre sur lui-même ne change pas son
contenu. Par contre, si tous ses bits sont à 0, le résultat donne 0 donc le
drapeau Z est mis à 1. Et RET Z fait un retour si Z = 1, donc si A = 0.
8-1-3 AND (ET)
Comme pour le OU, l’opération se fait entre 2 bits. Si l’un ET
l’autre des bits sont à 1, alors le résultat est 1.
0 ET 0 = 0
0 ET 1 = 0
1 ET 0 = 0
1 ET 1 = 1
C’est l’instruction AND s ( A <- A ET s) qui se charge de cette
opération entre tous les bits du registre A et s. En pratique, elle sert à
plusieurs choses :
Forcer quelques bits à 0 (inverse de OU pour les
1):
A = %1001 1110 dont
on voudrait forcer les 4 bits de poids faible à 0
B = %1111 0000
« masque ET »
AND B
=> A = 1001 0000
Les 4 bits de poids fort (1001) sont inchangés et les 4 bits de poids faible
sont à 0
Détecter si un groupe de bits est nul (tous à
0) :
Pour savoir si un bit est égal à 0 ou 1, il y a
l’instruction BIT n,r qui teste le bit n du registre r. Mais il se peut des
fois que l’on ait besoin de savoir si un groupe de bits est à 0 ou non. Pour
cela, on force tous les autres bits à 0 et on regarde le résultat. Exemple, on
teste les 4 bits de poids faible d’un nombre et on retourne s’ils sont tous à
0 :
LD A,(NBR)
;A <- Nombre
AND %00001111 ;Masque ET préservant les 4 bits de
poids faible
RET Z
;Retour s’ils sont tous à 0
8-4 XOR (OU exclusif)
Cette opération ressemble au OU, sauf quand les 2 bits sont
à 1. Dans ce cas le résultat est nul :
0 OUEX 0 = 0
0 OUEX 1 = 1
1 OUEX 0 = 1
1 OUEX 1 = 0
Deux façons de l’interpréter.
1) Soit on la considère comme une fonction OUI / NON
paramétrable :
_____
Exemple, on veut inverser les 4 bits de poids faible d’un
nombre sans toucher aux autres:
A = %1111 1010
XOR %0000 1111
=> A = %1111 0101
(les 4 bits de poids faible ont été inversés)
2) Soit on la considère comme une fonction de comparaison :
_____
Exemple, bien qu’il soit préférable de passer par
l’instruction CP pour comparer 2 nombres:
LD A,(NBR2)
LD B,A
; B = nombre 2
LD A,(NBR1)
; A = nombre 1
XOR
B ; A = B ?
RET Z ; Oui => retour
On utilise aussi XOR A pour un remise à 0 de A :
XOR
A : A = 0
8-2 Comparaisons
8-2-1 Les nombres signés
Comme nous l’avons vu au chapitre premier, les nombres
peuvent être considérés comme strictement positifs non signés (0 à 255 pour les
octets ou 0 à 65535 pour les mots de 16 bits) ou signés (-128 à +127 pour les
octets ou –32768 à +32767). Dans ce cas, leur notation est la suivante :
Octet |
Non
signé |
Signé |
Mot de
16 bits |
Non
signé |
Signé |
&00 |
0 |
0 |
&0000 |
0 |
0 |
... |
|
|
... |
|
|
&7F |
127 |
127 |
&7FFF |
32767 |
326767 |
&80 |
128 |
-128 |
&8000 |
32768 |
-32768 |
&81 |
129 |
-127 |
&8001 |
32769 |
-32767 |
... |
|
|
... |
|
|
&FF |
255 |
-1 |
&FFFF |
65535 |
-1 |
Le microprocesseur possède 2 drapeaux (ou
flags) permettant d’étudier le signe d’une opération:
Z : mis à 1 si résultat arithmétique = 0
S : mis à 1 si résultat arithmétique négatif et à 0 si
résultat positif ou nul.
A noter que l’instruction JR ne prend en compte que les
drapeaux Z et C, mais pas S. Ce dernier est cependant géré par les instructions
CALL, JP et RET. Et les conditions ne sont pas NS et S, mais plutôt P
(Plus +) et M (Minus -)
JP P,SAUT ; saute
si S = 0
JP M,SAUT ; saute
si S = 1
Savoir si un octet est négatif ou positif (en
signé seulement):
Si A < 0 Faire
... ; programme pour le négatif
Sinon
... ; programme pour le positif
Fin si
Peut se traduire par :
ORG
&4000
LD
A,(NBR) ; A <- nombre à tester
BIT
7,A ; test du bit 7 (poids fort)
JR
Z,SAUT ; bit = 0 => A positif =>
APOS
LD
A,’<’ ; Programme pour négatif
CALL &BB5A
RET
APOS LD A,’>’ ; programme pour positif
CALL &BB5A
RET
NBR DB &7F
Pour les nombres de 16 bits, il suffit de tester le bit 7 de
l’octet de poids fort.
8-2-2 Comparaison de 2 nombres
Il arrive souvent qu’on veuille tester 2 nombres.
Exemple :
A = nombre 1
B = nombre 2
Si A < B faire
...
Sinon si A = B faire
...
Sinon
... ( A > B )
Fin si
Fin si
Ca à l’air simple comme ça, mais il y a un bins ! La comparaison
de nombres égaux ne pose à priori pas de problèmes. Mais ça se complique selon
que l’on considère les nombres comme signés ou non signés. Prenons par
exemple :
A = &7F
B = &80
En non signé, A = 127 et B = 128, donc A < B.
En signé, A = 127 et B = -128, donc A > B.
Seul le programmeur sait s’il utilise des nombres signés ou non, car le
microprocesseur ne considère lui que le signe d’opérations arithmétiques sur
des nombres positifs. Donc, avec l’opération CP r (A – r en mémoire), il fera
127 – 128, trouvera –1 avec retenue (&FF + C) donc positionnera les flags C
et S à 1. Il faudra alors considérer 2 types de comparaisons. A noter que la
comparaison entre un nombre signé et non signé est idiote, puisque comment
différencier le nombre &80 = 128 et le nombre &80 = -128 ?
Comparaison non signée :
org &4000
LD A,(NBR2)
LD B,A ; B <- nombre 2
LD A,(NBR1) ; A <- nombre 1
CP B ; A - B en mémoire
JR NC, SAUT1 ; si A >= B => SAUT1
LD A,’<’ ;programme pour A < B
CALL &BB5A
RET
SAUT1 JR NZ, SAUT2 ; si A > B => SAUT2
LD A,’=’ ;programme pour A =
B
CALL &BB5A
RET
SAUT2 LD A,’>’ ;programme
pour A > B
CALL &BB5A
RET
NBR1 DB &81
NBR2 DB &80
Amusez-vous à changer les valeurs de NBR1 et NBR2.
Comparaison signée :
5 possibilités :
A = B
A > 0 et B > 0 : on fait A – B et si Carry = 0 alors A >
B et l’inverse sinon.
A > 0 et B < 0 : donc A > B.
A < 0 et B > 0 : donc A < B.
A < 0 et B < 0 : on fait A – B et si Carry = 0 alors A >
B et l’inverse sinon.
org &4000
LD A,(NBR2)
LD B,A ; B <- nombre 2
LD A,(NBR1) ; A <- nombre 1
CP B ; A - B en mémoire
JR NZ, SAUT1 ; si A != B => SAUT1
LD a,'=' ; Programme pour A =
B
CALL &BB5A
RET
SAUT1 BIT 7,A ; A < 0 ?
JR NZ,ANEG ; oui => ANEG
BIT 7,B ; B < 0 ?
JR NZ, SUP ; oui => A > B => SUP
COMP CP
B ; A - B en mémoire
JR
NC, SUP ; Pas de retenue => A >
B => SUP
INF
LD a,'<' ; programme pour
A < B
CALL &BB5A
RET
ANEG BIT 7,B ; B < 0 ?
JR Z, INF ; non => INF
JP COMP ; sinon on compare A
et B => COMP
SUP
LD a,'>' ; programme pour A > B
CALL &BB5A
RET
NBR1 DB &80
NBR2 DB &ff
Amusez-vous à changer les valeurs NBR1 et NBR2.
8-3 Les boucles de programme
Imaginez un programme sans rebouclage, où l’on recopierait
250 fois le même morceau de code pour quelque chose qui nécessite 250
répétitions. Dans certains cas d’optimisation extrême du temps machine, ça peut
peut-être arriver, mais sinon ça n’a aucun intérêt sinon de gonfler inutilement
le programme. Il faut donc faire appel à des boucles de programme qui se
chargent des répétitions, jusqu’à ce qu’une condition soit atteinte.
8-3-1 Boucle FOR
La plupart des langages informatiques possèdent la fameuse
boucle FOR, à commencer par le BASIC. Rappelez-vous :
10
FOR I = 0 TO 10
20
PRINT”Bonjour! “
30
NEXT I
En langage algorithmique, c’est la boucle POUR:
POUR I = 0 A 10 FAIRE
Afficher
« Bonjour ! »
FIN POUR
Qui affiche 10 fois “Bonjour! “ à l’écran. En assembleur
cette instruction n’existe pas mais il est possible de la créer assez facilement.
Toutefois, le microprocesseur étant plus sensible à la détection de 0 qu’à la
détection d’un nombre, on préfèrera commencer par un nombre positif en le
décrémentant jusqu’à 0. En assembleur, pour des boucles de 255 max, on peut
utiliser DJNZ e, qui décrémente le registre B et reboucle si différent de
0 :
ORG
&4000
LD
B,10 ; nombre de boucles à faire
(max 255)
boucl LD A,'a'
; affiche un a
CALL
&BB5A
DJNZ
boucl ; décrémente B et reboucle si
different de 0
RET
Il faut juste faire attention que le programme, contenu dans
la boucle, ne modifie pas le registre B bien entendu. Pour les bouclages
supérieurs à 255, sur 16 bits par exemple, c’est un peu plus délicat. En effet,
les instructions de type DEC dd ou INC dd n’entraînent aucune modification de
drapeau. Donc ce qu’on fait, c’est qu’on part avec un nombre négatif qu’on
incrémente jusqu’à 0 et c’est en détectant le poids fort à 0 qu’on sait si on a
terminé. Mais attention car on ne peut pas dépasser 65281 boucles. En effet,
–65282 = &00FE, donc en l’incrémentant on obtient &00FF avec le poids
fort à 0.
org
&4000
LD
BC,-300 ; nombre de boucles noté en négatif (65281 max)
XOR A ; A = 0
boucl
PUSH AF
LD A,'a' ; affiche un a
CALL &BB5A
POP AF
INC
BC ; incrémente BC
CP
B ; poids fort = 0?
JR
NZ,boucl ; non => on reboucle
RET
8-3-2
Boucles WHILE et DO WHILE
Ou TANT QUE et FAIRE TANT QUE, en gros:
While :
TANT QUE (condition = vraie) FAIRE
…
FIN TANT QUE
Do while :
FAIRE
…
TANT QUE (condition = vraie)
Dans un cas, on teste d’abord la condition avant de faire la
boucle. Et dans l’autre, on fait la boucle d’abord, puis on teste la condition.
Dans le cas du petit programme d’exemple, on avait une boucle TANT QUE, puisque
l’on testait d’abord si la lettre était nulle avant de l’afficher et de
reboucler. Mais il se peut qu’on veuille exécuter la boucle une fois avant de
tester :
Boucl
...
...
LD A,(HL)
OR A
JR NZ,boucl
; Si A est different de 0, on reboucle
... ; A = 0
8-4 Sous-programmes
Un sous-programme est un morceau de programme qu’on appelle par
un CALL et qui se termine par un RET. Donc le programme d’exemple est en soit
un sous-programme, puisqu’il est appelé par le BASIC de la même manière. Mais à
l’intérieur de celui-ci, il est possible de créer d’autres sous-programmes que
l’on appellera par l’instruction Z80 CALL. Reprenons le programme
d’exemple ; plutôt que de le retaper à chaque fois qu’on veut écrire une
phrase, on peut s’y prendre de la manière suivante :
; Programme principal
org &4000
LD HL,TEXTE1 ;HL
pointe le début de la phrase 1
CALL PRINTF
;Affiche la phrase
LD HL,TEXTE2 ;HL
pointe sur la phrase 2
CALL PRINTF
;Affiche la phrase
RET
TEXTE1 DB "Phrase 1",13,10,0
;Phrase 1 (caractères 13+10 = retour à la ligne)
TEXTE2 DB "Phrase 2",13,10,0
;Phrase 2
; Sous-programme PRINTF
affichant la phrase pointée par HL
PRINTF PUSH
AF ;sauvegarde de AF
PRINT1 LD A, (HL) ;A <- caractère pointé pas HL.
OR A ;A
<- A OU A (détection de 0)
JR Z,PRINT2 ;Si A=0 => PRINT2
CALL &BB5A
;vecteur d'affichage d'un caractère
INC HL ;HL
pointe le caractère suivant
JR PRINT1
;revient à PRINT1
PRINT2 POP AF ;AF restitué
RET ;Retour
de sous-programme
On a créé un sous-programme appelé PRINTF (PRINT étant une
directive), du nom de l’étiquette qui lui sert d’entrée pour les CALLs. Ainsi,
à chaque fois que l’on veut afficher une phrase, on fait pointer HL sur
celle-ci et on appelle ce sous-programme. Maintenant, il a fallut le
retravailler un peu par rapport à la première version. En effet, celui-ci
modifie le registre A pour son fonctionnement et si l’on ne veut pas que le
programme appelant soit perturbé par ce changement, il faut pouvoir sauvegarder
A avant. C’est ce qu’on fait avec le PUSH AF en entrée (on sauvegarde le
registre 16 bits entier), et le POP AF en sortie. Ainsi, au lieu de RET Z après
le OR A, on fait un saut conditionnel à PRINT 2, qui se charge de terminer le sous-programme
de manière propre. Attention, on se rappelle que la pile est de type LIFO (last
in first out), donc dernier mémorisé premier sorti :
PUSH DE
PUSH HL
…
POP HL
POP DE
Vous avez aussi remarqué qu’on avait utilisé 2 caractères
non imprimables en fin de phrase: le 13 et le 10. Le 13 permet de replacer
le curseur texte en début de ligne, et le 10 permet de passer à la ligne
suivante. Donc au total, on a un retour à la ligne.
8-5 Rotations et décalages
Le Z80 possède 10 types de rotation et 3 types de décalages.
Ca fait beaucoup pour s’y retrouver ! Essayons de les voir ensemble :
ORG &4000
LD A,(OCTET) ; A
= Octet a afficher en binaire
LD B,8 ;
Nombre de bits à analyser
ROTATE RLCA ; Rotation à gauche
JR C,BIT1 ;
Bit de retenue à 1? => BIT1
LD C,’0’ ; On
s’apprête à afficher ‘0’
JR AFFICHE
BIT1 LD C,’1’ ; On s’apprête à afficher ‘1’
AFFICHE PUSH
AF
LD A,C
CALL
&BB5A ; Affiche 0 ou 1 selon C
POP AF
DJNZ ROTATE ;
puis reboucle si analyse non terminée
RET
OCTET DB %10101010
Attention : C
signifie soit la retenue, soit le registre.
RLA : comme
RLCA, sauf que cette fois la retenue C intervient dans le rebouclage. Celle-ci
est recopiée dans le bit 0 et le bit 7 est recopié dans la retenue C.
RRCA : Comme RLCA mais à droite.
RRA : Comme RLA mais à droite.
RLC r ou (HL) ou (IX+d) ou (IY+d) : RLC pour registres B,C,D,E,H,L,
en indirect (HL), ou en indexé. Attention ces opérations prennent plus de place
et sont moins rapides que RLCA.
RL s, RRC s, RR s : similaires à RLA, RRCA et RRA, mais pour les
registres B,C,D,E,H,L, en indirect (HL), ou en indexé. Exemple, rotation à
gauche d’un nombre de 16 bits :
ORG &4000
LD BC,(NBR16) ; BC
= nombre de 16 bits
LD A,C ;
mémorisation du poids faible
RLCA ;
rotation à gauche juste pour la retenue
RL B ; rotation du poids fort avec retenue
RL C ;
rotation du poids faible avec retenue
LD (NBR16),BC ; mémorisation du résultat
RET
NBR16 DW &7FFF
SLA s : décalage
d’un bit à gauche du registre ou case mémoire s. Le bit de poids faible est mis
à 0 et le bit 7 est placé dans la retenue C. C’est une multiplication par 2, ce
qui peut être très utile dans les multiplications fixes. L’exemple suivant
multiplie un octet par 5 (5A = 4A + A).
ORG &4000
LD A,(NBR8) ; A
= nombre à multiplier
LD B,A ;
Pour le +A
SLA
A ; 2A
SLA
A ; 4A
ADD
A,B ; 5A
LD
(NBR8),A
NBR8 DB 10
Pour le décalage à gauche d’un nombre de 16 bits, c’est
carrément plus simple, car il suffit de passer par HL :
ORG &4000
LD HL,(NBR16) ; HL
= nombre de 16 bits
ADD HL,HL ;
Décalage à gauche de HL !
LD (NBR16),HL ; mémorisation du résultat
RET
NBR16 DW &7FFF
SRA s : décalage d’un bit à droite de s, avec recopie du bit
7 sur lui-même. Les décalages à droite servent généralement de division sans
reste. En effet, décaler 4 donnera bien 2, mais décaler 5 donnera 2 aussi (pas de
reste, pas de virgule). La recopie du bit 7 sert pour les nombres signés :
soit ils sont positif et on recopie 0, soit ils sont négatifs et on recopie
1 :
A = %1111
1110 (-2)
SRA A
A = %1111
1111 (-1)
SRL s : décalage à droite de s, mais avec mise à 0 du bit 7. Vous
l’aurez compris, c’est la même chose mais pour les nombres non signés. Pour le
décalage à droite d’un nombre de 16 bits, il faut passer par une
rotation :
ORG &4000
LD BC,(NBR16) ; BC
= nombre de 16 bits
SRL B ;
Décalage à droite du poids fort
RR C ;
Rotation du poids faible avec retenue
LD (NBR16),BC ; mémorisation du résultat
RET
NBR16 DW &FFFE
RLD et RRD : échangent les 4 bits de poids fort et de poids faible
de A. C’est une rotation de 4 bits mais sans retenue. Cette fonction peut être
utile en BCD.
8-6 Multiplication 8 x 8 bits – résultat sur 16 bits
Le Z80 ne possède pas de multiplication de 2 nombres de 8
bits. Et pour cause, le calcul se ferait sur 16 bits, trop pour un
microprocesseur 8 bits. Mais ce n’est pas grave puisqu’on peut la faire
soi-même. Prenons la multiplication suivante :
10111100
x
01001001
-----------------
10111100
10111100
10111100
-----------------
=0011010110011100
Déjà on voit bien que le résultat est sur 16 bits. Ensuite
on prend le multiplicateur (nombre du bas). On regarde le bit le plus faible,
il est égale à 1 donc on additionne le
multiplicande (nombre du haut) au résultat. Puis dans le résultat
intermédiaire, on décale ce multiplicande de 1 à gauche. Ensuite on regarde le
bit suivant : 0, donc on ne fait rien et on redécale encore le
multiplicande. 3ème bit même chose. 4ème bit = 1, donc on
additionne le multiplicande décalé 3x au résultat. Même problème pour les bits
suivants.
Donc qu’est-ce qui se passe ? après l’analyse de chaque
bit du multiplicateur, on décale le multiplicande. Mais si le bit courant est à
1, on l’additionne d’abord au résultat final. Sinon on ne fait rien.
ORG
&4000
LD
A,(NBR2)
LD
C,A ; C = Multiplicateur
LD
A,(NBR1)
LD
E,A ; E = Multiplicande
LD D,0
LD B,8 ;
Nombre de bits
LD HL,0 ; HL
= résultat
NXTB SRL
C ; Décaler le multiplicateur à
droite
JR
NC,NOADD ; Bit = 0 => NOADD
ADD
HL,DE ; ajouter le multiplicande
décalé au résultat
NOADD SLA E
RL D ;
Multiplicande décalé à gauche
DJNZ NXTB ;
reboucle tant que pas terminé
LD (RESULT),HL ; Résultat mémorisé
RET
NBR1 DB 08
NBR2 DB 64
RESULT DW 0
8-7 Division 16 / 8 bits – quotient sur 16 bits
Soit un dividende de 16 bits et un diviseur de 8 bits. Le
résultat sera contenu dans un quotient de 16 bits et un reste de 8 bits. Si on
peut soustraire le diviseur du poids fort du dividende sans débordement, on
augmente le quotient de 1 et on recommence. Sinon, on décale à gauche le
dividende et le quotient, et on recommence. Au final, ce qui n’a pas pu être
soustrait, c’est le reste
ORG &4000
LD HL,(DIVID) ; HL
= dividende
LD A,(DIVIS)
LD C,A ; C
= diviseur
LD B,9 ;
Nombre de bits + 1
LD DE,0 ;
Quotient
BOUCL1 LD A,H ; A = poids fort du dividende
SLA E
RL D ;
Décalage à gauche de DE
BOUCL2 CP C ; A - C en mémoire
JR C,NXTB ; si
retenue, sauter à NXTB
SUB C ;
sinon soustraction du diviseur
INC DE ;
Incrémente le quotient
JP BOUCL2 ; et
recommence
NXTB LD H,A
ADD
HL,HL ; décale le dividende à
gauche
DJNZ BOUCL1 ; Si pas fini => BOUCL1
LD (RESTE),A ; A
= reste
LD (QUOT),DE ; DE
= Quotient
RET
DIVID DW
&7FFE
DIVIS DB &10
QUOT DW 0
RESTE DB 0
Quotient = &07FF et le reste = &0E .
8-8 - Bloc de données mémoire
8-8-1 Données 16 bits
Quand une donnée 16 bits est sauvegardée en mémoire, il faut
bien se rappeler que c’est l’octet de poids faible qui est d’abord sauvegardé à
l’adresse, puis l’octet de poids fort à l’adresse + 1 :
ECRAN EQU &C000
DATA EQU &FF00
ORG &4000
LD HL,ECRAN
LD (HL),DATA
RET
(&C000) = &00
(&C001) = &FF
Les données de 16 bits font 2 octets, donc pour les adresser il faut
prévoir une incrémentation de 2. Le programme suivant, bien qu’inutile,
illustre la démarche :
ORG
&4000
LD
B,4 ; nombre de données à
récupérer
LD
HL,DATA ; HL pointe les données
courantes
BOUCLE LD
E,(HL) ; E = poids faible
INC
HL ; HL + 1
LD D,(HL)
; D = poids fort
INC HL ; HL + 1
DJNZ BOUCLE
RET
DATA DW
&FF00,&00FF,&F0F0,&0F0F
8-8-2 Copie de blocs mémoire
Il existe 4 instructions de copie de blocs mémoire :
LDI = copie (HL) dans (DE), incrémente HL et DE, puis
décrémente BC. Cette instruction sert à recopier une case mémoire dans une
autre. BC sert ici de compteur, mais il faut prévoir le code nécessaire au
rebouclage si l’on veut copier plusieurs données :
LD
HL,&B992 ; HL = adresse de départ
LD
DE,&C000 ; DE = adresse de destination
LD
BC,&A1 ; BC = nombre d’octets + 1
BOUCLE
LDI ; Copie une donnée
JP
P0,FIN ; Sort de la boucle si BC = 1
JP
BOUCLE ; Sinon continue
FIN RET
La condition JP P0,FIN saute si le résultat (ici de BC – 1),
possède un nombre de bits impairs. Mais LDI s’arrange pour que cela n’arrive
que quand BC = 1, cette astuce est donc propre à cette instruction. L’intérêt
de LDI est que l’on peut mettre du code entre chaque recopie (entre les 2 JP).
Ceci permet par exemple de créer une boucle de délai pour ralentir la recopie
d’un bloc mémoire.
LDIR : Comme LDI mais continue tant que BC est différent de
0. Cette fois, l’instruction recopie le bloc mémoire d’un trait.
LD
HL,&B992 ; HL = adresse de départ
LD
DE,&C000 ; DE = adresse de destination
LD
BC,&A0 ; BC = nombre d’octets à
copier
LDIR ; Recopie de bloc
RET
Recopie 160 octets (&A0) mémorisés à partir de
&B992, dans la mémoire à partir de &C000 (jusqu’à &C09F inclu
donc).
LDD : Comme LDI mais décrémente HL et DE. Sert quand on
veut recopier un bloc mémoire en partant de la fin. Les conditions sur BC
restent les mêmes.
LDDR : comme LDIR mais décrémente HL et DE.
8-8-3 Comparaison de bloc mémoire
Comme pour les recopies de données, il existe 4 instructions de
comparaison de données avec un bloc mémoire :
CPI : compare A avec le contenu de HL, puis incrémente HL
et décrémente le compteur BC. Comme pour LDI, n’agit qu’un fois, il faut donc
prévoir le rebouclage adéquat.
CPIR : même chose, mais continue tant que BC est différent
de 0.
CPD : même
chose que CPI, mais décrémente HL.
CPDR : même chose que CPIR, mais décrémente HL.