Nous commençons dans ce quatrième numéro de CPC Oxigen une série d'articles sur la programmation en assembleur. Ces leçons traiterons de la programmation bas niveau du microprocesseur Z80. Les problèmes que nous aborderons ne seront pas spécifiques au CPC. Le code pourra, à quelques détails près, être exécuté sur tout système à base de Z80 : Amstrad CPC, PCW,Sinclair, ZX 80/81, etc. Nous supposons que vous connaissez les principes de bases du microprocesseur Z80 : nous n'expliquerons pas ce qu'est un registre, ni quels sont les registres du Z80. Nous n'expliquerons pas l'arithmétique binaire, ni les nombres hexadécimaux. Nous supposons que vous disposez des outils logiciels nécessaires pour utiliser les morceaux de code que vous trouverez dans ces articles.
Le contenu exact de ces articles n'est pas très bien défini. Nous commencerons par des fonctions mathématiques simples. Pour finir, nous écrirons un compilateur C++ complet, et ses bibliothèques (hem... dans le courant des vingt prochaines années, ok ? ;o). Les prochains épisodes dépendrons de vos réactions. Donnez votre opinion, envoyez-nous vos questions, soumettez-nous des problèmes, ...
Z80 est un processeur très ancien. Même comparé au 8088 d'Intel (l'ancêtre du Pentium), Elle est lente et pas très puissante. Oh ! Comprenez-moi : j'aime cette puce ! Je L'aime comme j'aime me rappeler mon premier baiser : pas le meilleur, mais Elle a changé ma vie.
Z80 est simple et efficace. Elle sait ajouter deux nombres. Elle sait soustraire un nombre d'un autre. Mais Elle ne sait pas multiplier, ni diviser. Nous devons donc L'aider un peu.
Voici la méthode la plus simple pour multiplier deux nombres :
Code 1.1:
; Le premier nombre est dans a ; Le second nombre est dans l ; Le résultat sera dans a ; Les deux nombres doivent être compris entre 0 et 15 (4 bits) ld b,l ; initialisation de l'index de boucle ld h,a ; on mémorise a dans h xor a ; fait a = 0 ; comme ld a,0 plus court Debut: add a,h ; a = a + h djnz Debut ; b = b - 1 ; si b <> 0 alors aller à Debut Fin: ; Le résultat est dans a, entre 0 et 255 (8 bits)
C'est très simple. Et même simpliste. Et très inefficace.
Essayez de vous rappeler comment faire pour multiplier deux nombres. Avec un crayon et un papier, bien sûr. Je sais, c'est si loin, vous étiez si jeunes, les ordinateurs sont si pratiques, etc. Mais essayez tout de même. Je vais vous aider avec un exemple :
123 x 45 ------ 615 492- ------ = 5535
Vous vous rappelez maintenant ? Oui ! C'est ça !
D'abord vous multipliez le premier nombre (123) par 5 (le chiffre le moins significatif de 45) : vous obtenez 615. Ensuite vous multipliez le premier nombre par 4 (le second et dernier chiffre de 45), puis pas 10 : vous obtenez 4920. Ajoutez cela au résultat précédent : 615+4920 = 5535.
Maintenant, un autre exemple, plus simple :
0110 x 1101 --------- 0110 (a) 0000- (b) 0110-- (c) 0110--- (d) --------- = 1001110
Simple, n'est-ce pas ?
Oups ! 0+1+1=...0 ? 0+1=...0 ? Non, bien sûr. J'ai oublié de vous préciser : cette opération utilise des nombres binaires : 1+1 = 10
Les chiffres binaires ont un côté vraiment sympa : ils sont très simples à multiplier, et le Z80 fait ça très vite !
0 x 1 = 0 1 x 1 = 1 nimportequoi x 0 = 0 quelquechose x 1 = lamemechose
Pas de retenu. Jamais.
Jetons un coup d'il sur notre exemple :
Il y a une manière un peu différente de faire cette opération :
Pas très clair ? Bon... Le code assembleur va peut-être vous aider.
Code 1.2:
; Le premier nombre est dans a ; Le second nombre est dans l ; Le résultat sera dans a ; Les deux nombres doivent être compris entre 0 et 15 (4 bits) ld h,a ; h contiendra un résultat intermédiaire ld b,4 ; initialisation de l'index de boucle : 4 bits xor a ; fait a = 0 ; comme ld a,0 plus court Debut: sra l ; décale l vers la droite et récupère le bit extrait dans la retenue (carry) jr nc,BitSuiv ; si le bit extrait est 0, ne rien faire add a,h ; sinon a = a + h BitSuiv: sla h ; décale h vers la gauche : comme h = h * 2 djnz Debut ; b = b - 1 ; si b <> 0 aller à Debut Fin:
Il y a de nombreuses manières pour faire ça. Celle-ci utilise quatre registres (a, b, h et l) et est longue de 13 octets.
Hé ! Le premier morceau de code est meilleur ! Il utilise aussi quatre registres, mais est long de seulement 6 octets !
Vous avez raison; le premier exemple est plus court. Mais la boucle est exécutée l fois en moyenne, c'est-à-dire huit fois. Dans le second cas, la boucle est exécutée seulement 4 fois : les valeurs des opérandes n'ont pas d'importance.
En fait, vous avez vraiment raison : le premier exemple est meilleur dans tous les cas. Parce que pour simplifier nous avons choisi de travailler avec seulement quatre bits. Si nous multiplions deux nombres de 8 bits, la première méthode boucle 128 fois en moyenne (256/2). La seconde méthode boucle seulement 8 fois (8 bits). Donc la seconde est meilleure quand les opérandes dépassent une certaine valeur. Jetez un il sur les exercices ci-dessous, et envoyez-nous vos solutions : les meilleurs seront publiées ici.
Amusez-vous bien.
L'exemple 1.1 ne retourne pas Zéro quand le second nombre est Zéro (in c). Corrigez ça.
L'exemple 1.1 fonctionne avec des opérandes supérieurs à 15 (plus de 4 bits). Mais il y a débordement si le résultat ne tient pas dans un octet. Corrigez ça et récupérez le résultat dans hl (registre 16 bits).
Corrigez l'exemple 1.2 et récupérez le résultat dans hl.
Comparez précisément les deux méthodes en terme de cycle machine. Vous pouvez utiliser une table d'opcodes, vous en trouverez sur le web.