CODINGANTOINE ★ ASSEMBLEUR Z80 - La division ★

Z80: La divisionCoding Antoine

Pour que notre Z80 puisse diviser un nombre binaire par un autre, nous allons utiliser la meme méthode que pour la multiplication: la méthode égyptienne ! Si vous vous souvenez bien, le principe de base consistait à dire que multiplier un nombre par un autre revient à le multiplier par chacune des puissances de deux qui composent le multiplicateur, et tout était réglé par quelques décalages et additions. Ainsi, avec la méthode égyptienne, l'opération A*10 se décomposait en A*8+A*2. On serait donc tenté de faire de meme pour la division... Cependant, il y a un hic, car on ne peut pas développer une division n'importe comment: A/10 n'est pas égal à A/8+A/2 !!!

Revenons donc un peu en arrière: résoudre une division revient à trouver X égal à A/B (pour ceux qui ne comprendraient pas, continuez à regarder Le Bebete Show...), c'est-à-dire tel que A=B*X. Donc ce n'est pas le diviseur B que l'on va décomposer, mais le dividende A !!! En effet, plusieurs fois de suite, on va multiplier B par des puissances de deux décroissantes (par exemple 16,8,4,etc...) d'exposant C, puis, si on peut soustraire B à A (B<A), on le fait et on met le bit C du résultat à 1, sinon on met juste ce meme bit à 0. Enfin, dans les deux cas, on décrémente C et on boucle jusqu'à C=0. Ca, c'est dans la théorie. Dans la pratique, ça donne ça:

10 ORG &A000 ; Il y a quelques différences avec la
20 LD A,(NUMBER2) méthode théorique: tout d'abord, au lieu
30 OR A ; de multiplier à chaque fois le diviseur
40 RET Z ; par une puissance de deux différente, on
50 LD B,0 ; calcule tout d'abord sa valeur maximale
60 LOOP ADD A,A ; (dans la boucle LOOP), puis on le divise
70 INC B ; par deux (RR C) à chaque parcours de la
80 JR NC,LOOP ; boucle principale DIV_LOOP.
90 RRA ;Par ailleurs, les différents bits du
100 LD C,A ; résultat sont modifiées à l'aide d'une
110 LD A,(NUMBER1) simple rotation (RL E), car les
120 LD E,0 instructions de manipulation de bits du
130 DIV_LOOP CP C ; Z80 (RES et SET) ne désignent les numéros
140 CCF ;de bits que par adressage immédiat et non
150 PUSH AF ; par adressage implicite (phrase
160 RL E ; volontairement incompréhensible pour le
170 POP AF ; commun des programmeurs...). Vous
180 JR NC,NOT ;remarquerez qu'à la fin, A contient le
190 SUB C ; reste de la division entière (aussi
200 NOT RR C ; appelé modulo).
210 DJNZ DIV_LOOP ; Notez également que, si cette routine
220 LD (REST),A ;est prete à l'emploi, elle n'en pas pour
230 LD A,E ; autant optimisée pour des raisons de
240 LD (RESULT),A ;clarte... Notamment, les PUSH et POP AF,
250 RET ;qui servent ici à sauvegarder l'état du
260 NUMBER1 DEFB 0 ; Carry, mais font quand meme, à eux deux,
270 NUMBER2 DEFB 0 ; 7 cycles machine (en clair, 7
280 RESULT DEFB 0 ; microsecondes), auraient pu etre évités
290 REST DEFB 0 ; sans beaucoup de difficultes.

D'autre part vous remarquerez qu'il y a deux boucles, dont la première ne sert qu'à préparer le contenu du diviseur (le registre C) et pourrait etre supprimée. C'est ce que nous allons voir avec la routine suivante...

10 DIV LD D,A ; En effet, cette routine est une routine
20 XOR A ; de division 16 par 8 bits avec résultat sur 8
30 LD E,A ; bits également. On profite donc de la phase de
40 SRL D ; conversion du diviseur de 8 à 16 bits pour le
50 RR E ; multiplier par 256, évitant ainsi une première
60 LD C,8 ; boucle de préparation comme dans la routine
70 DIV_LOOP OR A ; précédente. Ensuite on le divise une première
80 SBC HL,DE fois par 2 avant de rentrer dans la boucle
90 JR NC,DIV_YES elle-meme. Pourquoi effectuer cette division
100 ADD HL,DE avant la boucle me direz-vous, puisqu'on la
110 DIV_YES CCF ; retrouve ensuite à l'intérieur de cette
120 RLA ; fameuse boucle DIV_LOOP ? Oui bon c'est que je
130 SRL D ; suis très flemmard et, quand je me suis posé
140 RR E ; la meme question que vous, je n'avais pas
150 DEC C ; vraiment envie de retoucher à ma routine... Si
160 JR NZ,DIV_LOOP vous voulez absolument gagner 4 microsecondes,
170 RET ; vous devrez déplacer les SRL D/RR E au début
180 END ; de la boucle (juste avant le OR A).

(Paramètres: HL=dividende, A=diviseur; résultat: A=quotient, HL=reste (modulo)).

Maintenant voici le temps pris par ces 2 premières routines :

  • Division 8 bits : mini. 35 cycles maxi. 210 cycles
  • Division 16/8 bits : mini. 155 cycles maxi. 171 cycles

Au niveau rapidité pur, la première routine est donc légèrement plus avantageuse, mais la seconde est beaucoup plus stable et régulière quels que soient les paramètres, ce qui peut etre très utile dans le cas de programmes nécessitant une synchronisation assez précise.

Voici maintenant une troisième routine, qui elle vous réalisera une division sur 32 bits purs. Les paramètres sont à transmettre aux adresses suivantes: NUMBER1 pour le dividende, NUMBER2 pour le diviseur. A la fin de la routine, le quotient et le modulo seront contenus respectivement aux labels RESULT et REST. Les 4 octets qui codent chacun de ces entiers seront stockés, comme pour les mots 16 bits, sous le format octet faible-octet fort.

10 ORG &A000 ;:: 350 ;CCF
20 DI ;:: 360 JR C,YES
30 LD HL,(NUMBER2) :: 370 NO ADD HL,DE
40 EXX ; :: 380 EXX
50 PUSH BC ; :: 390 ;ADC HL,DE
60 PUSH DE ; :: 400 EXX
70 PUSH HL ; :: 410 OR A
80 LD HL,(NUMBER2+2) :: 420 YES RL C
90 LD A,H ; :: 430 ;RL B
100 OR L ; :: 440 EXX
110 EXX ; :: 450 ;RL C
120 OR H ; :: 460 ;RL B
130 OR L ; :: 470 ;SRL D
140 JR Z,OVER ; :: 480 ;RR E
150 LD A,1 ; :: 490 EXX
160 PREP INC A ; :: 500 ;RR D
170 ADD HL,HL ;:: 510 ;RR E
180 EXX ; :: 520 DEC A
190 ADC HL,HL ;:: 530 JR NZ,LOOP
200 EXX ; :: 540 LD (RESULT),BC
210 JP P,PREP ; :: 550 LD (REST),HL
220 EXX ; :: 560 EXX
230 EX DE,HL ;:: 570 LD (RESULT+2),BC
240 LD HL,(NUMBER1+2) :: 580 LD (REST+2),HL
250 LD BC,0 ; :: 590 OVER POP HL
260 EXX ; :: 600 POP DE
270 EX DE,HL ;:: 610 POP BC
280 LD HL,(NUMBER1) :: 620 EXX
290 LD BC,0 ; :: 630 EI
300 LOOP OR A ; :: 640 RET
310 SBC HL,DE ;:: 650 NUMBER1 DEFS 4,0
320 EXX ; :: 660 NUMBER2 DEFS 4,0
330 SBC HL,DE ;:: 670 RESULT DEFS 4,0
340 EXX ; :: 680 REST DEFS 4,0

Je vous conseille fortement de décortiquer cette routine, ça sera surement très instructif. Si vous vous demandez pourquoi j'utilise les registres alternatifs - qui, rappelons-le, obligent à sauvegarder les anciennes valeurs et à inhiber les interruptions -, sachez que cette routine est à peu près trois fois plus rapide qu'une routine équivalente qui utiliserait les registres 16 bits comme pointeurs sur des cases mémoires. N'hésitez jamais à utiliser les registres alternatifs si vous n'avez pas assez d'un seul jeu de registres et que vous avez des contraintes de rapidité à respecter.

C'est enfin terminé pour cette fois-ci ! De toute façon c'en est fini de l'arithmétique qui commencait à devenir très très chiante. La prochaine fois je vous ferai peut-etre un cours sur l'optimisation et tout ça... En attendant vous pouvez toujours m'écrire si vous avez des problèmes de programmation (assembleur mais aussi Basic), des questions sur le hard ou tous autres genres de trucs.

Bye...

Antoine / POW pour MM&PF

★ ANNÉE: ???
★ AUTEUR: ANTOINE

CPCrulez[Content Management System] v8.7-desktop
Page créée en 141 millisecondes et consultée 2860 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.