Partie 8 : algorithmes de base

 

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 :

 

  1. NOT (NON)
  2. OR (OU)
  3. AND (ET)
  4. XOR (OU exclusif)

 

 

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 :

 

RLCA : rotation à gauche du registre A. Au passage, le bit 7 est recopié dans la retenue (drapeau C). Cette instruction peut-être intéressante quand on veut analyser tous les bits d’un octet. L’exemple suivant affiche la valeur binaire d’un octet :

 

         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.