CPC-POWER : CPC-SOFTS - CPCArchives 
Options de Recherche :
 
 
 

ARTICLES

42 / 72

Cours sur l'assembleur Z80 - algorithmes de base

COURS D’ASSEMBLEUR - 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
         OR A           ;A
         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.

 

 

A) 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)

 

 

A-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

 

 

A-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

 

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
OR A         ;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.

 

 

A-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

 

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
AND %00001111 ;Masque ET préservant les 4 bits de poids faible
RET Z         ;Retour s’ils sont tous à 0

 

 

A-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 :

  • Bit 1 = 0 => S = Bit 2
  • Bit 1 = 1 => S = Bit 2

 

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 :

  • Bit 1 = Bit 2 => S = 0
  • Bit 1 = Bit 2 => S = 1

 

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

 

 

B) Comparaisons

B-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
      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.

 

 

B-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
      LD A,(NBR1)  ; A
      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
      LD A,(NBR1)  ; A
      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.

 

 

C) 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.

 

 

C-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

 

 

C-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

 

 

D) 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
         OR A           ;A
         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.

 

 

E) 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.

 

 

F) 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

 

 

G) 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

 

 

H) - Bloc de données mémoire

H-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

 

 

H-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.

 

 

H-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.

 

Article rédigé par Christophe PETIT

 

 

Article créé le : Samedi 26 Septembre 2015 à 21 h 34
Dernière mise à jour le : Samedi 26 Septembre 2015 à 21 h 53
 
 

CPC-POWER/CPCArchives, projet maintenu par Fredouille.
Programmation par Kukulcan © 2007-2017 tous droits réservés.
Reproduction sans autorisation interdite. Tous les titres utilisées appartiennent à leurs propriétaires respectifs.