CODINGANTOINE ★ ASSEMBLEUR Z80 - La Multiplication ★

Z80: La MultiplicationCoding Antoine

Eh oui ce mois-ci marque le début d'un nouveau dossier sur l'arithmétique. A propos d'arithmétique, cette discipline o combien  noble  est  en  train  d'etre réinventée par Amstrad 100 %; prenez le numéro 42 et lisez un peu la page 31 ... 2+2+1+1=8 !!! Etonnant, non ?? Pour revenir à notre dossier, il  ne  s'agit  pas d'apprendre l'addition à Poum, mais de faire en langage machine  des  opérations un peu plus complexes que celles directement réalisables avec les mnémoniques du Z80.

Mais pourquoi chercher à faire ses propres  routines  alors  qu'on  pourrait utiliser celles qui sont en ROM ? Pour cela il faut se souvenir que  le  système peut traiter deux types de nombres: les entiers 16 bits et les nombres à virgule flottante codés sur 5 octets, dont 4 pour le nombre  lui-meme,  et  un  pour  la mantisse, c'est-à-dire l'exposant en puissance de deux.

En pratique, on constate que ces  deux  types  de  nombres  sont  assez  peu adaptés à nos besoins. En effet les opérations sur les entiers sont rapides mais ceux-ci sont limités de -32768 à 32767. D'autre  part,  les  nombres  à  virgule flottante permettent un champ extremement vaste, mais  les  opérations  sur  ces nombres sont très complexes et très longues... De plus, pour le  programmeur  en assembleur, si les opérations à  virgule  flottante  sont  accessibles  par  les vecteurs système, il n'en est pas de meme pour  l'arithmétique  entière  qui  se situe dans la ROM Basic, et donc il faut faire un saut  directement  dans  cette Rom, ce qui demande  tout  d'abord  de  repérer  les  adresses  exactes  de  ces routines, et de plus cela supprime la compatibilité entre les différents modèles de CPC.

Nous allons donc commencer ce mois-ci par la  multiplication.  Bien  sur  il vient en premier à l'esprit l'idée d'additionner n fois le nombre à lui-meme, ce qui donnera bien comme résultat le produit des deux :

                          LD B,MULTI
                          LD DE,NOMBRE
                          LD HL,0
                   BOUCLE ADD HL,DE
                          DJNZ BOUCLE

C'est en effet la  définition  meme  de  la  multiplication.  On  s'aperçoit cependant vite qu'à partir d'un certain nombre (B=16 environ),  la  routine  est trop lente. Avec un multiplicateur sur 8 bits, c'est déjà long, alors je ne vous conseille pas d'essayer une multiplication 32 bits par cette méthode !!

Il faut alors penser à certains cas particuliers... en effet que fait-on par exemple pour multiplier un nombre par 2,4,8 ou toute autre puissance de deux  en assembleur ? Vous me répondrez bien sur que vous faites un truc du genre ADD A,A ou ADD HL,HL et ceci 1,2,3 fois ou plus selon l'exposant n de  la  puissance  de deux 2^n.

Pour  généraliser  il  nous  faut  un  peu  d'algèbre...  Par  exemple  pour multiplier l'accu par  9, on serait tenté de faire:

                          LD C,A
                          LD B,9
                   BOUCLE ADD A,C
                          DJNZ BOUCLE

Mais si on cherche autre  chose,  on  trouve  9=8a+a...  J'espère  que  vous comprenez ça !! Or, la multiplication par 8 et par 1, on sait faire...

                          LD E,A
                          ADD A,A
                          ADD A,A
                          ADD A,A
                          ADD A,E

On voit bien que les trois ADD A,A reviennent à  faire  une  rotation  de  3 bits vers la gauche, ou encore à une multiplication par 8... Ensuite  on  ajoute l'ancien contenu de A, ce qui fait bien une multiplication par 9 !! Je me répète mais c'est pour etre sur que vous ayez bien compris...  Maintenant  la  question est de savoir si tout nombre, quel qu'il soit, peut etre converti en  une  somme de puissances de deux...  C'est  là  qu'il  va  falloir  vous  souvenir  de  vos premiers cours d'assembleur, que  dis-je,  de  votre  premier  contact  avec  le

binaire... En effet de quoi est constitué un nombre  binaire,  hein  ?  Mais  de puissances de deux bien sur  !!  Rappelez-vous...  chaque  bit  d'un  octet  est associé à une puissance de deux.

Ainsi le bit 0 correspond à 2^0=1, le bit 1 à 2^1=2, ainsi de suite jusqu'au bit n qui correspond à 2^n. Tout devient  ainsi  limpide,  il  nous  suffira  de tester tous les bits, du bit de poids faible au bit de poids fort, et  d'ajouter si ce bit est à un, 2^n au résultat, n étant le 'numéro' du bit. Tout ceci  sera effectué très simplement à l'aide de rotations et d'additions.

A titre d'exemple, voici une routine qui  effectue  la  multiplication  d'un nombre 16 bits par un multiplicateur 8 bits, avec un résultat stocké  lui  aussi sur 2 octets:
 

  10           ORG  &A000            Explications:  à  chaque  parcours  de   la
  20           LD   HL,(NOMBRE)      boucle LOOP, la  rotation  charge  dans  le
  30           LD   A,(MULTI)        Carry   l'ancien   état   du   bit   0   de
  40           LD   DE,0             l'accumulateur. Si le carry est  à  un,  on
  50           LD   B,8              ajoute à DE le nombre à multiplier HL. Puis
  60 LOOP      RRA                   on multiplie HL par deux pour tenir  compte
  70           JR   NC,LOOP_SUI1     du rang des différents bits.
  80           EX   DE,HL            Après avoir parcouru la boucle 8 fois  (car
  90           ADD  HL,DE            le multiplicateur est codé sur 8 bits),  on
 100           EX   DE,HL            charge  le  résultat  à  l'adresse  mémoire
 110 LOOP_SUI1 ADD  HL,HL            et c'est fini.
 120           DJNZ LOOP             Vous pouvez bien sur tester  cette  routine
 130           LD   (RESULT),DE      sous Basic après l'assemblage. A cet  effet
 140           RET                   n'oubliez  pas  que  les  nombres  16  bits
 150 NOMBRE    DW   0                doivent   etre   sous   le   format   octet
 160 MULTI     DB   0                faible-octet fort et que cette  routine  ne
 170 RESULT    DW   0                tient pas compte des retenues.

Cette méthode est très rapide puisque la boucle est executée un nombre  de fois correspondant  au  nombre  de  bits  du  multiplicateur:  8  fois  pour  un multiplicateur 8 bits, etc... Elle est appelée  la  méthode  égyptienne  (ne  me demandez pas pourquoi !) et c'est  elle  qui  est  utilisée, par exemple,  par l'arithmétique entière du Basic.

Mais bien sur, vous aurez surement besoin un jour de manipuler des nombres plus grands. Comme le Z80 n'a que peu  de  registres,  on  ne  pourra  donc  pas travailler directement sur  ces  registres  mais  sur  des  emplacements  de  la mémoire. Voici donc une routine qui effectue une multiplication d'un 'long word'(32 bits) par un mot (16 bits), avec un résultat sur 32 bits également...

  10           ORG  &A000          La structure  de  départ  est  gardée,  à  la
  20           LD   IX,NOMBRE      différence que j'ai séparé les  sous-routines
  30           LD   IY,RESULT      d'addition et de rotation 32 bits  pour  plus
  40           CALL INIT           de clarté.
  50           LD   HL,(MULTI)
  60           LD   B,16           La routine d'initialisation (label INIT)  est
  70 LOOP      RR   H              nécessaire pour mettre les 4 octets (soit  un
  80           RR   L              mot    long)    du    résultat    à     zéro.
  90           CALL C,ADD32        Si le multiplicateur fait plus  de  2  octets
 100           CALL ROTATION       (un mot), il faudra également le  traiter  en
 110           DJNZ LOOP           adressage indirect ou indexé.
 120           RET
 130 NOMBRE    DS   4
 140 MULTI     DS   2              Pour l'addition et la rotation 32  bits  j'ai
 150 RESULT    DS   4              choisi  des  routines  linéaires  car   elles
 160 INIT      LD   HL,RESULT      nécessitent très peu de mise au  point.  Pour
 170           LD   DE,RESULT+1    des nombres plus grands (64 bits ?), il  vaut
 180           LD   BC,3           mieux réserver les registres d'index pour  le
 190           LD   (HL),B         multiplicateur;   on   prendra   alors   pour
 200           LDIR                adresser le NOMBRE et le RESULTat en mémoire,
 210           RET                 les registres HL  et  DE,  et  on  effectuera
 220 ADD32     LD   A,(IY+0)       l'addition  et  la  rotation  à   l'aide   de
 230           ADD  A,(IX+0)       boucles.
 240           LD   (IY+0),A
 250           LD   A,(IY+1)       Vous trouverez sur la disquette le code objet
 260           ADC  A,(IX+1)       de cette routine sous le nom MULTI32.OBJ,  en
 270           LD   (IY+1),A       voici les adresses importantes:
 280           LD   A,(IY+2)
 290           ADC  A,(IX+2)          DEBUT : &A000
 300           LD   (IY+2),A          FIN   : &A06A
 310           LD   A,(IY+3)          EXECUTION: &A000
 320           ADC  A,(IX+3)
 330           LD   (IY+3),A          LABELS: NOMBRE      &A01D
 340           RET                            MULTI       &A021
 350 ROTATION  OR   A                         RESULT      &A023
 360           RL   (IX+0)
 370           RL   (IX+1)         Vous pouvez parfaitement tester cette routine
 380           RL   (IX+2)         sous Basic, pour cela un programme inclus sur
 390           RL   (IX+3)         la disquette vous permettra, après  que  vous
 400           RET                 avez  chargé  le  code  objet,  d'entrer  les
 410           END                 paramètres en mémoire (MULTI32.BAS).

Cette routine, bien  que  traitant  des  nombres  importants,  reste  rapide puisqu'elle  prend  au  pire  et  à  tout  casser  (&FFFFFFFF  *   &FFFF)   2,04 millisecondes !!! Maintenant si vous voulez travailler sur des  nombres  signés, il faut que votre routine détermine le signe du résultat d'après celui des  deux termes, qu'elle appelle la routine de multiplication sans signe avec les valeurs absolues des nombres, puis qu'elle change le signe du résultat selon ce  qu'elle a prévu auparavant.

Voilà, après ces quelques 10758 octets (!) de délire intellectuel,  j'espère que vous avez tout compris parce que je crois que j'aurais eu  du  mal  à  faire plus dilué... La prochaine fois vous aurez droit soit à  la  division,  soit  au format BCD et tout le  casse-tete  (pour  rester  poli...)  qu'il  engendre;  ça dépendra de l'état d'avancement de mes routines mais aussi et surtout  de  votre courrier. Ben oui c'est tout de meme vous qui lisez MMPF (Micro Mag & Press Fire bande d'ignares), c'est donc vous qui devez me dire que traiter dans le prochain numéro, alors écrivez-moi et n'oubliez pas le traditionnel timbre à 2,50  F.  si vous désirez une réponse personnelle.

Antoine / POW pour MM&PF6

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

CPCrulez[Content Management System] v8.75-desktop/c
Page créée en 045 millisecondes et consultée 1765 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.