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

cpcoxygen@hotmail.com

Programmation Z80

Leçon 1 - Multiplier

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.

Xavier Glattard

Exercices :

1 - Multiplication par Zéro.

L'exemple 1.1 ne retourne pas Zéro quand le second nombre est Zéro (in c). Corrigez ça.

2 - Opérateurs 8 bits (1ère partie)

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

3 - Opérateurs 8 bits (2ème partie)

Corrigez l'exemple 1.2 et récupérez le résultat dans hl.

4 - Opérateurs 8 bits (conclusion)

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.

Liens (en anglais) :

  1. DEVSEEK Programming Languages Assembly z80
  2. Thomas Scherrer Z80-Family Official Support Page
  3. Z80 Documentation - ticalc.org
  4. Zophar's Domain Z80 Technical Documents
  5. ETC - ZiLOG Z80
  6. Information on the Z80 processor