CODINGANTIBUG ★ LES MULTIPLICATIONS ET LES DIVISIONS ENTIÈRES AVEC LE Z80 PAR ANTIBUG ★

Les multiplications et les divisions entières avec le Z80 par ANTIBUGCoding Antibug
Sommaire:

dernière maj : le 07/05/2007

Releases:

— le 07/05/2007: modification de la fonction de division en 5.2.3 - Division de deux entiers non signé de 16 bits, résultat sur 16 bits

1 - Préface

Je sais que de nombreux articles ont déjà été rédigés à ce sujet, mais néanmoins je vais y ajouter quelques informations supplémentaires. L'utilisation de la multiplication avec le Z80 n'est pas forcément de tout repos. Comme vous avez pu le constater, les simples multiplications et divisions n'existent pas sur le Z80, mis à part les décalages binaires. Le but de ce cours est de redévelopper ces deux opérations comme elles devraient être intégrées au cœur du microprocesseur. Dans un premier temps nous aborderons le problème de la multiplication avec son aspect théorique et pratique, et dans un second temps la division. Cet article ne parlera pas des méthodes avec des tables pré-calculées.

2 - Nombres signés et compléments de bit

Il existe trois méthodes pour indiquer qu'un nombre est signé :

  • La notation en grandeur exacte
  • Le complément à 1
  • Le complément à 2

Pour le Z80, c'est le complément à 2 qui est utilisé mais nous allons étudier ces trois méthodes en détail.

2.1 La notation en grandeur exacte

Cette notation utilise le dernier bit d'un nombre pour indiquer si celui-ci est positif (à 0) ou négatif (à 1), exemple :

Notation exacte de 31 :

b7

b6

b5

b4

b3

b2

b1

b0

0

0

1

1

1

1

1

1

Notation exacte de -31 :

b7

b6

b5

b4

b3

b2

b1

b0

1

0

1

1

1

1

1

1

La seule différence entre ces deux exemples est le bit 7 qui sert de bit de signe.

2.2 Le complément de bit à 1

Pour indiquer qu'un nombre est négatif en notation complément à 1, il suffit d'inverser tous les bits d'un nombre négatif tout en gardant le bit de signe, illustration :

Notation de 24 :

b7

b6

b5

b4

b3

b2

b1

b0

O

0

0

1

1

0

0

0

Complément à 1 de -24 :

b7

b6

b5

b4

b3

b2

b1

b0

1

0

0

1

1

0

0

0

En notation exacte

b7

b6

b5

b4

b3

b2

b1

b0

1

1

1

0

0

1

1

1

Notation en complément à 1

Important : Le signe n'est pas complémenté ! Le complément à 1 est la notation exact d'un nombre avec inversion de ses bits.

2.3 Le complément de bit à 2

La notation en complément à 2 s'obtient en prenant la notation en complément à 1 et en additionnant 1 au nombre ; le bit de signe lui, ne bouge pas, illustration :

Notation de 24 :

b7

b6

b5

b4

b3

b2

b1

b0

0

0

0

1

1

0

0

0

Complément à 2 de -24 :

b7

b6

b5

b4

b3

b2

b1

b0

1

0

0

1

1

0

0

0

En notation exacte

b7

b6

b5

b4

b3

b2

b1

b0

1

1

1

0

0

1

1

1

Notation en complément à 1

b7

b6

b5

b4

b3

b2

b1

b0

1

1

1

0

1

0

0

0

Notation en complément à 2

La plupart des microprocesseurs utilise une notation en complément à 2 pour indiquer qu'un nombre est négatif. Les conversions dans les différentes notations ne sont pas compliquées. Pour passer d'une notation complément à 1 en notation exacte, il suffit de complémenter de nouveau chaque bit du nombre (pas le bit de signe), de même, pour passer d'une notation complément à 2 vers une notation exacte il suffit de complémenter de nouveau chaque bit et d'ajouter 1 au nombre obtenu, tout en gardant le bit de signe.

Complément à 1 vers notation exacte : %10011011 => %11100100 <=> -100 %00100101 => %00100101 <=> 80 (pas de changement car positif)

Complément à 2 vers notation exacte : %10110001 => %11001111 <=> -79 %00011100 => %00011100 <=> 28 (pas de changement car positif)

2.4 Multiplication ou division d'un nombre signé

Sachant que la plupart des microprocesseurs utilisent la notation complément à 2 pour traiter les nombres négatifs, pour multiplier ou diviser un nombre négatif nous transformerons d'abord le nombre négatif en nombre positif, nous effectuerons l'opération de multiplication ou division, puis nous complémenterons de nouveau le nombre obtenu à 2.

3 – Les multiplications

3.1 Les généralités

Pour aborder la multiplication et pour ne pas vous faire peur, sachez que les nombres binaires se multiplient de la même façon que les nombres décimaux. Le calcul de la multiplication est même plus simple en binaire car les chiffres du multiplicateur ne peuvent être que 0 ou 1. Voyons directement comment formaliser cette multiplication non singée:

Comme vous pouvez le constater, ce calcul n'est pas compliqué ! Pour calculer le produit d'une telle opération les microprocesseurs doivent cumuler les produits partiels deux à deux (sauf les microprocesseurs de dernière génération à cache interne). Cette opération étant inexistante dans le Z80 il nous reste plus qu'à réinventer la roue. Voyons maintenant comment nous allons structurer notre programme étape par étape :

Algorithme :

Il faut savoir que le résultat d'une opération de 2 entiers sur 8 bits est toujours égal à un entier sur 16 bits. Nous allons voir plus tard comment coder une routine de multiplication de deux entiers de 8 bits. 3.2 Passage en revue des solutions existantes

La solution miracle est celle qui sera adaptée à votre programme. Nous allons voir ensemble quelques solutions existantes.

3.2.1 La multiplication par décalages (2exp n)

Cette multiplication est la plus simple, celle-ci joue sur le fait qu'un décalage d'un bit vers la gauche équivaut à une multiplication par 2, exemple : LD A, 1 ; A = 1 <=> %0001 SLA A ; A = 2 <=> %0010 SLA A ; A = 4 <=> %0100

Comme vous avez pu le remarquer nous avons multiplié A par 2 puis par 4, cette multiplication équivaut avec multiplier A par 2exp n.3.2.2 La multiplication par additions

Contrairement à la méthode précédente, la multiplication par addition n'est pas limitée à un multiple de 2 ; cette méthode est basée sur l'utilisation d'additions dans les calculs et permet d'utiliser un multiplicateur quelconque. Cependant, celle-ci peut s'avérer relativement longue lorsque vous avez à calculer (1000 * 1000), ex : LD HL, 0 LD DE, 1000 LD B, 1000 _boucle ADD HL, DE DJNZ _boucle


Je pense que vous imaginez bien que le fait d'effectuer 1000 fois une addition n'est certainement par envisageable ? Vous utiliserez donc cette méthode si vous savez que vous n'avez pas de très grosses opérations à effectuer.

3.2.3 La multiplication par retenues

La multiplication par retenue est basée sur l'utilisation le flag Carry pour détecter un débordement lors des calculs. En effet, nous allons étudier un exemple de multiplication de 2 valeurs sur 8 bits. Rappelez-vous qu'une multiplication binaire n'est qu'une multiplication de 1 et de 0 où chaque nombre 1 rencontré est multiplié par autant de nombres 1 que le multiplicateur en possède, exemple : %0001 x %0011 = %0011 %0010 x %0011 = %0110 %0100 x %0011 = %1100 %0101 x %0011 = %1111 %0001 x %0010 = %0010 %0011 x %0010 = %0110


Je pense que l'exemple est très parlant ! C'est important de comprendre cela car l'exemple que nous allons traiter fonctionne de la même manière. Pour multiplier 2 nombres entiers sur 8 bits, nous allons décaler bit à bit le premier nombre (le multiplicande), pour chaque bit à 1 nous allons le multiplier par les bits du second nombre (le multiplicateur) ; ceci est une sorte de remplacement de bits. Pour détecter si le dernier bit (le plus haut à gauche) du multiplicande est à 1 nous allons utiliser l'indicateur Carry, celui-ci se mettra à 1 lorsqu'un débordement se produira. Passons directement à notre exemple.

Multiplication de 2 nombres entiers de 8 bits chacun (65 x 2), le résultat est contenu dans un entier de 16 bits :


Rappel sur les différents flags du registre F

;***************************************************************
; Exemple de multiplication de 2 entiers non signés, de 8 bits
;
; En entrée :
; H <=> Multiplicande
; E <=> Multiplicateur
; En sortie :
; HL <=> Produit de H par E
; Registres affectés
; F, HL, DE, B
MATH_MUL_8x8 :

LD L, 0
LD D, L
LD B, 8

_MATH_MUL_8x8_1

; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
ADD HL, HL
JR nc, _MATH_MUL_8x8_2

; Remplacement du bit de débordement par le multiplicateur
ADD HL, DE

_MATH_MUL_8x8_2

DJNZ _MATH_MUL_8x8_1
RET

Cet exemple ce code var servir pour notre démonstration. Prenez un peu de temps pour étudier ce programme, même si celui-ci est très simple faites quelques tests sur papier. Quelque soit le nombre en entrée, ce programme effectuera quand même une rotation du multiplicande, des 8 bits. Regardons maintenant en détail ce qui se passe :


1ère étape :

Pour H = 65, L = 0, D = 0, E = 2, B = 8

2ème étape :

3ème étape :

n ème étape :

7ème étape :

8ème étape :

Au final, tous les bits à 1 du multiplicande ont été remplacés par ceux du multiplicateur, ceci équivaut au produit de 65 x 2 = 130 <=> %10000010.

3.2.4 La multiplication par ajout de sous-produits

La multiplication par sous-produits a été détaillée en 2.1 dans « Les généralités ». Si vous avez bien étudié sont fonctionnement, vous verrez que le nombre d'opérations (d'additions) dépend du nombre de bits du multiplicateur. En effet nous avons autant de sous-additions que de bits à 1 dans le multiplicateur. Cette méthode est celle employée dans les microprocesseurs, les plus modernes optimisent l'addition des sous produits via leur système de cache. Nous allons directement passer à la partie code :

;***************************************************************
; Exemple de multiplication de 2 entiers non signés, de 8 bits
;
; En entrée
; A <=> Multiplicateur
; B <=> Multiplicande
; En sortie
; HL <=> Produit de A et de B (résultat sur 16 bits)
; Registres affectés
; F, HL, A, B
MATH_MUL_8x8:

LD HL,0 ; Mise à blanc de HL pour le retour

OR A ; Si le A = 0 on sort
RET z
CP B ; Vérification si A < B
JP c, _MATH_MUL_8x8_1
LD L, A ; Echange du multiplicande avec le
LD A, B ; multiplicateur pour traiter moins de bits
LD B, L ; possible
LD L, H
OR A ; Si le A = 0 on sort
RET z

_MATH_MUL_8x8_1

PUSH DE ; On préserve DE pour le retour
LD D, L ; Le multiplicande (B) est stocké dans
LD E, B ; DE pour calculer les sous-produits

_MATH_MUL_8x8_2

BIT 0, A ; Vérification si le bit 0 = 0, si oui
; on ne cumule pas le sous-produit courant
JP z, _MATH_MUL_8x8_3
ADD HL, DE ; Cumule du sous-produit courant

_MATH_MUL_8x8_3

EX HL,DE ; Décalage du sous-produit courant
ADD HL,HL ; de 1 bit vers le haut
EX HL,DE ;
SRL A ; Décalage du multiplicateur de 1 bit
; vers le bas
OR A ; Vérification que tous les bits du
; multiplicateur ont été traités
JP nz, _MATH_MUL_8x8_2

_MATH_MUL_8x8_END

POP DE ; Restauration de DE
RET

Détails du code Z80 :

Dans un premier temps HL est initialisé à 0 pour le retour, ensuite A est comparé à 0 de manière à éviter d'effectuer des calculs inutiles. Ensuite A est comparé à B pour voir si le multiplicateur (A) est supérieur au multiplicande (B) ; ci c'est le cas on échange les contenus de A et de B, rappelez-vous que les calculs qui sont effectués dépendent du nombre de bit à 1 dans le multiplicateur (A), plus A est petit moins les calculs sont important. Ensuite nous rentrons dans les calculs proprement dits, DE est initialisé avec B, un premier test est effectué sur le bit 0 de A, si celui-ci est égal à 0 on saute au calcul du sous-produit suivant, sinon on ajoute le sous-produit courant à HL (qui est le produit final), etc. etc. Le calcul du sous-produit correspond à un simple décalage de 1 bit, du multiplicande, vers la gauche, à chaque fois qu'un bit du multiplicateur est analysé.

Lorsque tous les bits du multiplicateur ont été traités (A = 0) on sort de la fonction, HL contient alors le produit final. L'utilisation d'une telle méthode est intéressante, la consommation en cycles dépend du nombre de bits dans le multiplicateur, elle évite d'exécuter un très grand nombre d'additions, dans une boucle par exemple. Rappelez-vous que le schéma détaillé se trouve au chapitre généralités.

4 – Les divisions

4.1 Les généralités

Comparé à la multiplication la division reste très simple dans son principe mais un peu plus complexe lors de la phase de codage. Nous verrons d'abord son principe général puis deux exemples concrets de code.

Exemple : Division du nombre 157 (%10011101 en binaire) par 5 (%101)

Le principe est de trouver des sous-dividendes supérieurs ou égaux au diviseur, puis d'effectuer une soustraction entre le sous-dividende courant et le diviseur; effectuer cette opération jusqu'à ce que tous les bits du dividende aient été traités. Voyons comment cela fonctionne étape par étape :

Algorithme :

4.2 Passage en revue des solutions existantes

4.2.1 La division par décalages (2exp n)

Comme pour la multiplication, la division par décalages est exactement la même méthode utilisée que pour la multiplication, seul le sens du décalage change. La division par décalage équivaut à une division par 2exp n, exemple :LD A, 4 ; A = 4 <=> %0100 SRL A ; A = 2 <=> %0010 SRL A ; A = 1 <=> %0001

4.2.2 La division par utilisation de sous-dividendes

Le principe de cette méthode est décrit au 4.1 dans « Les généralités », nous allons voir concrètement comment coder cette méthode. Mettons en pratique l'algorithme décrit plus haut:

Exemple :

;***************************************************************
; Routine de division de 2 entiers non signés, de 8 bits
;
; En entrée
; A <=> Dividende
; B <=> Diviseur
; En sortie
; A <=> Quotient
; B <=> Reste de la division entière
; Registres affectés
; F, A, B
;
MATH_DIV_8x8:

PUSH DE ; Préservation de DE
LD D, A ; Le dividende et le diviseur sont stockés
LD E, B ; respectivement dans D et E
LD B, 8 ; Les 8 bits du dividende sont traités
XOR A, A

_MATH_DIV_8x8_1

SLA D ; Décalage du bit le plus haut, du dividende dans Carry
RLA ; Récupération de Carry dans le sous-dividende
; (création du sous dividende)
CP E ; Vérification si le sous-dividende est supérieur ou
; égal au diviseur. Si oui le quotient = + 1, sinon on
; traite le bit suivant, du dividende
JR c, _MATH_DIV_8x8_2
INC D ; Incrémentation du quotient. Sachant que les bits de D
; sortent vers le haut (la gauche), les bits du bas vont
; servir pour stocker le quotient
SUB E ; On soustrait le diviseur du dividende

_MATH_DIV_8x8_2

DJNZ _MATH_DIV_8x8_1 ; Décrémentation de B de manière à
; à traiter les 8 bits du dividende
LD B, A ; Récupération du reste dans B
LD A, D ; Récupération du quotient calculé dans A
POP DE ; Restauration de DE
RET

Prenez un peu de temps pour étudier ce code, il n'est pas compliqué mais il faut bien comprendre tous les enchaînements. Comme pour la division, c'est à vous d'utiliser telle ou telle méthode, dans certains cas seuls des décalages seront suffisants dans d'autres vous aurez à implémenter une méthode plus complexe.

Nous allons reprendre cet exemple mais avec la division d'un nombre entier 16 bits par un nombre entier de 16 bits :

Exemple :

;***************************************************************
; Routine de division d'un entier non signé de 16 bits, par un
; entier non signé de 16 bits
;
; En entrée
; BC <=> Dividende
; DE <=> Diviseur
; En sortie
; BC <=> Quotient
; DE <=> Reste de la division entière (sur 16 bits)
; Registres affectés
; BC, DE
;
MATH_DIV_16x16:

PUSH AF ; Préservation de AF
PUSH HL ; Préservation de HL

LD HL, 0 ; Servira de sous-dividende
LD B, A ; Stockage de la partie haute du diviseur dans B
LD B, 16 ; Les 16 bits de HL (le dividende) vont être traités
XOR A, A

_MATH_DIV_16x16_1:

SLA C ; Décalage du bit le plus haut de la partie basse du
; dividende, dans Carry
RLA ; Récupération de Carry dans la partie haute du dividende
; Et décalage de la partie haute du dividende dans Carry
ADC HL, HL ; Récupération éventuelle de Carry et création par la
; même occasion du sous-dividende
SBC HL, DE ; On enlève le diviseur du sous-dividende, si Carry est
; allumé c'est que le sous-dividende est inférieur
JR c, _MATH_DIV_16x16_2
INC C ; Incrémentation de C de manière à crée le quotient
; (Remplacement des bits du dividende par le quotient)

DJNZ _MATH_DIV_16x16_1 ; Décrémentation de B de manière à
; traiter les 16 bits du dividende

_MATH_DIV_16x16_2:

ADD HL, DE ; restauration du sous-dividende car celui-ci est
; inférieur au diviseur
DJNZ _MATH_DIV_16x16_1 ; Décrémentation de B de manière à
; traiter les 16 bits du dividende

LD D, H ; Récupération du reste de la division dans DE
LD E, L
LD B, A ; Partie haute du quotient dans B
; La partie basse se trouve déjà dans C
POP HL ; Restauration de HL
POP AF ; Restauration de AF
RET

Si vous avez bien regardé, vous avez remarqué que cette exemple est un peu plus complexe et nécessite d'éclaircir certains points. Dans le code on peut remarquer que le dividende de la division de trouve initialement dans BC puis transféré dans A, pour la partie haute, et C pour la partie basse. En effet, HL va contenir le sous-dividende, le fait de décaler C puis A va permettre de faire sortir les bits par le haut (la gauche de A) puis de les récupérer un à un dans HL via une addition avec retenue.

Pour tester si le sous-dividende (HL) est bien supérieur ou égal au diviseur (DE), l'utilisation de l'opérante CP est impossible, on va donc effectuer une soustraction avec retenue pour contrôler le contenu des deux registres. Si HL est bien supérieur ou égal à DE, on réinjecte un bit à 1 dans C, pour créé le quotient au fur et à mesure, dans le cas contraire on rétablit le sous-dividende et C sera décalé naturellement avec un bit à 0.

Pour conclure, en ce qui concerne la multiplication et la division, je pense que si vous avez bien étudié toutes les méthodes de calcul. Vous verrez qu'au final ce n'est pas très compliqué ! La dernière partie de cet article rassemble plusieurs fonctions sur la multiplication et la division, fonctions que vous pourrez réutiliser dans vos programmes.

Dans le prochain article nous étudierons le rapport entre les opérations, la pile et les registres spécialisés, @bientôt ...
5 – Bibliothèque de méthodes

5.1 Les multiplications par retenues

5.1.1 Multiplication de 2 entiers non signés sur 8 bits, résultat sur 16 bits

;***************************************************************
; Routine de multiplication de 2 entiers non signés, de 8 bits; avec un résultat sur 16 bits
;
; En entrée :
; A <=> Multiplicande
; B <=> Multiplicateur
; En sortie :
; HL <=> Produit de A par B
; Registres affectés
; F, HL; Remarques
; - Si A ou B = 0 => HL = 0 en sortie
; - A et B sont préservés
;MATH_MUL_8x8: LD HL, 0 ; Valeur de retour
; Vérification des valeurs en entrée
OR A ; Test de A
RET z ; Si A = 0 on sort
LD L, A ; Sauvegarde de A pour le test de B
LD A, B ; B est transféré dans A pour le test
OR A ; Test de B via A
LD A, L ; On restore A
LD L, H ; On efface L via H
RET z ; Si B = 0 on sort
; Début de code lié à la multiplication
PUSH BC ; Préservation de BC
PUSH DE ; Préservation de DE
LD D, L ; D est effacer par L
LD E, A ; Le multiplicande va dans E
LD H, B ; Le multiplicateur va dans H
LD B, 8 ; Traitement des 8 bits
_MATH_MUL_8x8_1 ; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
ADD HL, HL
JR nc, _MATH_MUL_8x8_2 ; Remplacement du bit de débordement par le multiplicateur
ADD HL, DE_MATH_MUL_8x8_2
DJNZ _MATH_MUL_8x8_1
POP DE ; restauration de DE
POP BC ; Restauration de BC
RET

5.1.2 Multiplication de deux entiers non signés de 16 par 8 bits, résultat sur 16 bits

;***************************************************************
; Routine de multiplication de 2 entiers non signés, de 16 par
; 8 bits, avec un résultat sur 16 bits;
; En entrée :
; DE <=> Multiplicande
; A <=> Multiplicateur
; En sortie :
; HL <=> Produit de DE par A (résultat sur 16 bits !)
; Registres affectés
; F, HL
; Remarques
; - Si A ou DE = 0 => HL = 0 en sortie
; - A et DE sont préservés
;
MATH_MUL_16x8: LD HL, 0 ; Valeur de retour
; Vérification des valeurs d'entrée
OR A
RET z
ADC HL, DE ; Ajout de DE à HL de manière à vérifier si DE = 0
RET z ; Si DE = 0 => z = 1
; Début du code lié à la multiplication
PUSH AF ; Préservation de AF
PUSH BC ; Préservation de BC
LD HL, 0 ; Valeur de retour
LD B, 8 ; Traitement des 8 bits
_MATH_MUL_16x8_1 ; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
ADD HL, HL
ADD A, A
JR nc, _MATH_MUL_16x8_2
; Remplacement du bit de débordement par le multiplicateur
ADD HL, DE_MATH_MUL_16x8_2
DJNZ _MATH_MUL_16x8_1
POP BC ; Restauration de BC
POP AF ; Restauration de AF
RET

5.1.3 Multiplication de deux entiers non signés de 16 bits, résultat sur 16 bits

;***************************************************************
; Routine de multiplication de 2 entiers non signés de 16 bits,
; avec un résultat sur 16 bits
;
; En entrée :
; BC <=> Multiplicande
; DE <=> Multiplicateur
; En sortie :
; HL <=> Produit de BC par DE (résultat sur 16 bits !)
; Registres affectés
; F, HL
; Remarques
; - Si BC ou DE = 0 => HL = 0 en sortie
; - BC et DE sont préservés
;
MATH_MUL_16x16 :

LD HL, 0 ; Valeur de retour
; Vérification des valeurs en entrée
RL L ; Permet d'effacer Carry ADC HL, BC ; Vérification que BC = 0 RET z LD HL, 0 ADC HL, DE ; Vérification que DE = 0 RET z
; Début du code lié à la multiplication PUSH AF ; Préservation de AF PUSH BC ; Préservation de BC LD HL, 0 ; Valeur de retour LD A, B LD B, 16_MATH_MUL_16x16_1
; Multiplication par 2 de HL (décalage d'1 bit vers le haut) ADD HL, HL SLA C ; SLA suivi d'un RLA permet de réaffecter les bits de C RLA ; dans A en cas de débordement de C, donc de traiter ; les 16 bits du multiplicateur JR nc, _MATH_MUL_16x16_2
; Remplacement du bit de débordement par le multiplicateur ADD HL, DE
_MATH_MUL_16x16_2 DJNZ _MATH_MUL_16x16_1 POP BC ; Restauration de BC POP AF ; Restaurationde AF RET

5.2 Les Multiplications par sous-produits

5.2.1 Multiplication de deux entiers non signés de 8 bits, résultat sur 16 bits

;***************************************************************
; Routine de multiplication de 2 entiers non signés, de 8 bits
; avec un résultat sur 16 bits
;
; En entrée
; A <=> Multiplicateur
; B <=> Multiplicande
; En sortie
; HL <=> Produit
; Registres affectés
; F, HL
; Remarques
; - Si A ou B = 0 => HL = 0 en sortie
; - A et B sont préservés
; - Pour optimiser les calculs, vous pouvez vérifier que le
; multiplicateur est bien inférieur au multiplicande
;

MATH_MUL_8x8:

LD HL,0 ; Valeur pour le retour

; Vérification des valeurs en entrée
OR A ; Test de A
RET z ; Retour si A = 0
LD L, A ; Sauvegarde de A pour le test de B
LD A, B ; Transfert de B dans A pour le test
OR A ; Test de B via A
LD A, L ; On restore A
LD L, H ; On efface L via H
RET z ; Retour sir B = 0

_MATH_MUL_8x8_1

; Début du code lié à la multiplication
PUSH AF ; Préservation de AF pour le retour
PUSH DE ; On préserve DE pour le retour
LD D, L ; Le multiplicande est dans DE pour calculer les sous-produits
LD E, B ;

_MATH_MUL_8x8_2

BIT 0, A ; Vérification si le bit 0 = 0, si oui
; on ne cumule pas le sous-produit courant
JP Z, _MATH_MUL_8x8_3
ADD HL, DE ; Cumule du sous-produit courant

_MATH_MUL_8x8_3

EX HL,DE ; Décalage du sous-produit courant
ADD HL,HL ; de 1 bit vers le haut
EX HL,DE ;
SRL A ; Décalage du multiplicateur de 1 bit
; vers le bas
OR A ; Vérification que tous les bits du
; multiplicateur ont été traités
JP nz, _MATH_MUL_8x8_2

_MATH_MUL_8x8_END

POP DE ; Restauration de DE
POP AF ; Restauration de AF
RET

5.2.2 Multiplication de deux entiers non signés de 16 par 8 bits, résultat sur 16 bits

; ***************************************************************
; Routine de multiplication de 2 entiers non signés, de 16 par
; 8 bits, avec un résultat sur 16 bits
;
; En entrée
; DE <=> Multiplicande
; A <=> Multiplicateur
; En sortie
; HL <=> Produit de DE x A (résultat sur 16 bits !)
; Registres affectés
; F, HL
; Remarques
; - Si De ou A = 0 => HL = 0 en sortie
; - DE et A sont préservés
; - Pour optimiser les calculs, vous pouvez vérifier que le
; multiplicateur est bien inférieur au multiplicande
;

MATH_MUL_16x8

LD HL,0 ; Mise à blanc de HL pour le retour

;Vérification des valeurs en entrée
OR A ; Si A = 0 on sort
RET z
ADC HL, DE ; Si DE = 0 on sort
RET z

; Début du code lié à la multiplication
PUSH AF ; Préservation de AF pour le retour
PUSH DE ; Préservation de DE pour le retour
LD HL, 0

_MATH_MUL_16x8_1

BIT 0, A ; Vérification si le bit 0 = 0, si oui
; on ne cumule pas le sous-produit courant
JP z, _MATH_MUL_16x8_3
ADD HL, DE ; Cumule du sous-produit courant

_MATH_MUL_16x8_3

EX HL,DE ; Décalage du sous-produit courant
ADD HL,HL ; de 1 bit vers le haut
EX HL,DE ;
SRL A ; Décalage du multiplicateur de 1 bit
; vers le bas
OR A ; Vérification que tous les bits du
; multiplicateur ont été traités
JP nz, _MATH_MUL_16x8_1

_MATH_MUL_16x8_END

POP DE ; Restauration de DE
POP AF ; Restauration de AF
RET

5.3 La division par utilisation de sous-dividendes

5.3.1 Division de deux entiers non signés de 8 bits, résultat sur 8 bits

;***************************************************************
; Routine de division de 2 entiers non signés de 8 bits, avec un
; résultat sur 8 bits
;
; En entrée
; A <=> Dividende
; B <=> Diviseur
; En sortie
; A <=> Quotient
; B <=> Reste de la division entière
; Registres affectés
; F, A , B
; Remarques
; - A et B sont détruits et remplacé respectivement par
; le quotient et le reste de la division
;

MATH_DIV_8x8:


PUSH DE ; Préservation de DE
LD D, A ; Le dividende et le diviseur sont stockés
LD E, B ; respectivement dans D et E
LD B, 8 ; Les 8 bits du dividende sont traités
XOR A, A

_MATH_DIV_8x8_1

SLA D ; Décalage du bit le plus haut, du dividende dans Carry
RLA ; Récupération de Carry dans le sous-dividende (A)
; (création du sous dividende)
CP E ; Vérification si le sous-dividende est supérieur ou
; égal au diviseur. Si oui le quotient = + 1, sinon on
; traite le bit suivant, du dividende
JR c, _MATH_DIV_8x8_2
INC D ; Incrémentation du quotient. Sachant que les bits de D
; sortent vers le haut (la gauche), les bits du bas vont
; servir pour stocker le quotient
SUB E ; On soustrait le diviseur du dividende

_MATH_DIV_8x8_2

DJNZ _MATH_DIV_8x8_1 ; Décrémentation de B de manière à
; à traiter les 8 bits du dividende
LD B, A ; Récupération du reste dans B
LD A, D ; Récupération du quotient calculé dans A
POP DE ; Restauration de DE
RET

5.3.2 Division de deux entiers non signé de 16 et 8 bits, résultat sur 16 bits

;***************************************************************
; Routine de division d'un entier non signé de 16 bits par un
; entier non signé de 8 bits, avec un résultat sur 16 bits
;
; En entrée
; BC <=> Dividende
; A <=> Diviseur
; En sortie
; BC <=> Quotient (sur 16 bits)
; A <=> Reste de la division entière
; Registres affectés
; F, BC, A
; Remarques
; - BC et A sont détruits et remplacé respectivement par
; le quotient et le reste de la division
;

MATH_DIV_16x8:

PUSH HL ; Préservation de HL
LD HL, BC ; Stockage du dividende dans HL
LD C, A ; Stockage du diviseur dans C
LD B, 16 ; Les 16 bits de HL (le dividende) vont être traités
XOR A, A

_MATH_DIV_16x8_1

SLA L ; Décalage du bit le plus haut, du dividende dans Carry
RL H
RLA ; Récupération de Carry dans le sous-dividende (A)
; (création du sous dividende)
CP C ; Vérification si le sous-dividende est supérieur ou
; égal au diviseur. Si oui le quotient = + 1, sinon on
; traite le bit suivant, du dividende
JR c, _MATH_DIV_16x8_2
INC HL ; Incrémentation du quotient (sachant que tous les bits
; de HL sont décalé vers le haut (la gauche), les bits
; venant du bas vont servir pour stocker le quotient
SUB C ; On soustrait le diviseur du dividende

_MATH_DIV_16x8_2

DJNZ _MATH_DIV_16x8_1 ; Décrémentation de B de manière à
; à traiter les 16 bits du dividende
LD B, H ; Récupération du quotient calculé dans HL
LD C, L ; Le reste se trouve dans A
POP HL ; Restauration de HL
RET

5.3.3 Division de deux entiers non signé de 16 bits, résultat sur 16 bits

;***************************************************************
; Routine de division d'un entier non signé de 16 bits par un
; entier non signé de 16 bits, avec un résultat sur 16 bits
;
;En entrée
; BC <=> Dividende
; DE <=> Diviseur
;En sortie
; BC <=> Quotient
; DE <=> Reste de la division entière (sur 16 bits)
;Registres affectés
; BC, DE
; Remarques
; - BC et DE sont détruits et remplacé respectivement par
; le quotient et le reste de la division
;

MATH_DIV_16x16:

PUSH AF ; Préservation de AF
PUSH HL ; Préservation de HL

LD HL, 0 ; Servira de sous-dividende
LD A, B ; Stockage de la partie haute du diviseur dans A
LD B, 16 ; Les 16 bits de HL (le dividende) vont être traités
XOR A, A

_MATH_DIV_16x16_1

SLA C ; Décalage du bit le plus haut de la partie basse du
; dividende, dans Carry
RLA ; Récupération de Carry dans la partie haute du dividende
; Et décalage de la partie haute du dividende dans Carry
ADC HL, HL ; Récupération éventuelle de Carry et création par la
; même occasion du sous-dividende
SBC HL, DE ; On enlève le diviseur du sous-dividende, si Carry est
; allumé c'est que le sous-dividende est inférieur
JR c, _MATH_DIV_16x16_2
INC C ; Incrémentation de C de manière à crée le quotient
; (Remplacement des bits du dividende par le quotient)
DJNZ _MATH_DIV_16x16_1 ; Décrémentation de B de manière à
; traiter les 16 bits du dividende
JP _MATH_DIV_16x16_END ; Saut à la fin lorsque B=0_MATH_DIV_16x16_2
ADD HL, DE ; Restauration du sous-dividende car celui-ci est
; inférieur au diviseur
DJNZ _MATH_DIV_16x16_1 ; Décrémentation de B de manière à
; traiter les 16 bits du dividende
_MATH_DIV_16x16_END


LD D, H ; Récupération du reste de la division dans DE
LD E, L
LD B, A ; Partie haute du quotient dans B
; La partie basse se trouve déjà dans C
POP HL ; Restauration de HL
POP AF ; Restauration de AF
RET

par –( ANTIBUG )- le 30/03/2007
dernière maj : le 03/04/2007

contact : vincentbouffigny.cpc@free.fr
web : http://ovi.org.free.fr - http://projets.infos.free.fr

★ ANNÉE: 2007
★ AUTEUR: ANTIBUG

Page précédente : Structure de la mémoire écran de l'Amstrad CPC par ANTIBUG

★ AMSTRAD CPC ★ A voir aussi sur CPCrulez , les sujets suivants pourront vous intéresser...

Lien(s):
» Coding » Cours et initiation d'ANTIBUG
» Coding » Les opérations, la pile et les registres spécialisés par ANTIBUG
» Coding » Le tracé de lignes sur CPC et Z80
» Coding » Structure de la mémoire écran de l'Amstrad CPC par ANTIBUG
Je participe au site:

» Vous avez remarqué une erreur dans ce texte ?
» Aidez-nous à améliorer cette page : en nous contactant via le forum ou par email.

CPCrulez[Content Management System] v8.7-desktop/c
Page créée en 339 millisecondes et consultée 2490 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.