Calcul de la ligne inférieure.

Nous voici donc arrivé dans un chapitre important qui va d'une part nous permettre de faire un affichage de sprite où l'on veut, mais aussi de voir de nouvelles instructions bien utiles.

Comme nous l'avons vu dans le cours précédent, passer à la ligne suivante lorsque l'on est dans un bloc ligne n'est pas difficile.
Il suffit en effet d'ajouter #01 au poids fort et le tour est joué.

Ce pendant, arrivé sur la dernière ligne pixel du bloc ligne, ceci ne fonctionne plus.

La dernière ligne du bloc a pour adresse #4700.
Vous avez peut-être fait des tests sous basic pour trouver l'adresse de la ligne suivante (j'espère) et avez trouvé l'adresse: #4020.
Ceci à première vue peut paraître étrange puisque par rapport à l'adresse précédente on recule.

La solution se trouve en fait dans la longueur d'une ligne.
En effet, si l'on reprend la toute première ligne de l'écran, celle-ci commence en #4000.
Pour avancer dans cette même ligne on incrémente l'adresse (#4001 ; #4002 ; #4003...).
Hors une ligne fait 32 octets de longueur. La fin d'une ligne est donc en +31 (puisque le premier octet est en possition 0 de l'adresse.).
Et à quoi correspond 31 en Hexadécimal
#1F ! Le dernier octet de la première ligne est donc en #401F.
#4020 correspond donc à l'octet suivant !

Mais alors comment alons nous faire une routine pour tester d'une part quand il faut arreter d'ajouter #0100; d'autre part pour avoir la bonne adresse pour le bloc ligne suivant ?

Pour tester si on passe à un autre bloc de ligne ce n'est pas très difficile.
Comme chaque bloc de ligne commence par une adresse ou les bits 0 à 2 sont à 0 on pourra simplement tester si après l'incrémentation du poids fort c'est le cas pour ceux-ci.
Mais comment tester des bits me direz-vous ?
Je vais vous expliquer ce qu'on appelle des opérateurs booléens ou algèbre de Boole.
C'est quelque chose de très important à comprendre et retenir donc soyez attentif.

Algèbre de Boole:

L'algèbre de Boole ou calcul booléen peuvent aussi être appelées opérations logiques.

Je n'entre pas dans le détail de l'histoire de la création de ce mode de calcul, vous trouverez ces informations très facilement sur le net.

Sur nos machines, ces opérations se comptent au nombre de 3: Le XOR; Le AND et le OR.
Ces opérations ont une logique binaire. Comprendre par la qu'il faudra réfléchir en binaire pour comprendre ce qu'elles font.

Voyons donc ces opérations:

OR

Le OR est simple: si un bit est à 1 alors le resultat sera 1.
C'est en fait un mix des 1 des deux valeurs.

OR

Exemple:

                            %01011111
                        OR  %10110011
                            ---------
                            %11111111

XOR

Le XOR, lui garde l'un ou l'autre mais pas les deux. Comprendre par la que dès que deux bits sont identiques, le resultat sera 0.
Le XOR permet de mettre rapidement A=0 puisqu'il suffit de faire un XOR A. (je rappel au passage que tout resultat d'une opération 8bits se fait dans A.
Faire un XOR A c'est donc faire un XOR de A avec A.

XOR

Exemple:

                            %01011111
                        XOR %10110011
                            ---------
                            %11101100
                Cas du XOR A:
                            LD  A,%01011111
                            XOR A


                Donne:
                            %01011111
                        XOR %01011111
                            ---------
                            %00000000

AND

Enfin le AND donnera 0 dès qu'il y aura un 0.
Seuls les double 1 seront preservés
Le AND est donc très pratique dès qu'il s'agit de ne garder que certains bits puisqu'il suffit de mettre des 0 pour éliminer des bits (et donc de mettre à 1 pour que les doubles 1 soient conservés.

AND

Exemple:

                            %01011111
                        AND %10110011
                            ---------
                            %00010011

Instructions

Voici une liste des instructions existantes sur Z80. Le résultat est toujours dans A:

AND r8 Vous faites un AND d'un registre 8bits avec A.
AND data8 Vous faites un AND de A avec une valeur 8bits.
AND (HL) Vous faites un AND de A avec l'octet se trouvant à l'adresse HL.
AND (IX+disp) Comme le AND (HL) mais avec IX comme pointeur d'adresse.
AND (IY+disp) Comme le AND (HL) mais avec IY comme pointeur d'adresse.

OR r8 Vous faites un OR d'un registre 8bits avec A.
OR data8 Vous faites un OR de A avec une valeur 8bits.
OR (HL) Vous faites un OR de A avec l'octet se trouvant à l'adresse HL.
OR (IX+disp) Comme le OR (HL) mais avec IX comme pointeur d'adresse.
OR (IY+disp) Comme le OR (HL) mais avec IY comme pointeur d'adresse.

XOR r8 Vous faites un XOR d'un registre 8bits avec A.
XOR data8 Vous faites un XOR de A avec une valeur 8bits.
XOR (HL) Vous faites un XOR de A avec l'octet se trouvant à l'adresse HL.
XOR (IX+disp) Comme le XOR (HL) mais avec IX comme pointeur d'adresse.
XOR (IY+disp) Comme le XOR (HL) mais avec IY comme pointeur d'adresse.

Revenons donc à notre calcul.
Nous disions donc que nous pourrions tester si les bits 0 à 2 sont égaux à 0.
Pour cela il faudra supprimer tous les autres bits. Ainsi si les bits 0 à 2 sont effectivement à 0, tout l'octet le sera.

Pour ne garder que les bits 0 à 2, nous utiliseront donc l'instruction AND.
Un AND %00000111 permettra donc de garder ces trois bits. Comme le AND est une opération, celle-ci modifie les flags. Si le resultat est un octet=0 alors le flag Z sera mis.
Nous pouvons donc tester si le flag n'est pas mis, ce qui signifiera qu'on ne sort pas d'un bloc ligne.

                ;DE contient l'adresse de la ligne
                        INC D           ;On incrémente le poids fort de l'adresse pour passer à la ligne suivante dans un bloc ligne
                        LD  A,D         ;On copie le poids fort dans A pour pouvoir opérer dessus sans le modifier
                        AND %00000111   ;On ne garde que les bits 0 à 2
                        RET NZ          ;Si le resultat n'est pas = 0 alors on ne sort pas d'un bloc ligne et on a la nouvelle adresse

Le problème de savoir si on sort d'un bloc ligne est donc réglé. Nous pouvons donc passer au calcul dans le cas ou on en sort.

Dans ce cas la, nous l'avons vu plus haut, il nous faut ajouter la largeur d'une ligne au poids faible, soit 32.
Comme lors d'un débordement de bloc ligne, nos bits 0 à 2 ont été mis à 0 c'est parfait puisque l'on reprend l'adresse de début du bloc et nous pouvons lui ajouter directement 32 au poids faible.

                ;DE contient l'adresse de la ligne
                        INC D           ;On incrémente le poids fort de l'adresse pour passer à la ligne suivante dans un bloc ligne
                        LD  A,D         ;On copie le poids fort dans A pour pouvoir opérer dessus sans le modifier
                        AND %00000111   ;On ne garde que les bits 0 à 2
                        RET NZ          ;Si le resultat n'est pas = 0 alors on ne sort pas d'un bloc ligne et on a la nouvelle adresse
                        LD  A,E         ;On récupère le poids faible de l'adresse
                        ADD A,32        ;On additionne 32 à A
                        LD  E,A         ;On recopie dans le poids faible de l'adresse

Qu'ouis-je ? Qu'entends-je ? Je n'ai pas parlé de l'instruction ADD ?

ADD A,val est une instruction d'addition. Comme toute opération 8bits, le resultat est mis dans A. Vous additionnez donc la valeur au registre A.

Au stade ou nous en sommes, notre routine de calcul de ligne inférieure fonctionne presque correctement.
Presque ? Oui, car il y a tout de même un bug...
Regardons nos valeurs quand nous ajoutons 32 à chaque changement de bloc.

#4000 est l'adresse du début du premier bloc. On ajoute donc 32 (#20).
#4020... on continue.
#4040...
#4060...
#4080...
#40A0...
#40C0...
#40E0... Attention, le problème va arriver ici.
#4100 !!!

#4100 ne nous envoie pas 8 lignes plus bas comme d'habitude, mais à la deuxième ligne pixel de l'écran.
En effet souvenez vous, comme on incrémente le poids fort de l'adresse pour passer à la ligne suivante dans un bloc ligne, la première ligne étant en #4000, la deuxième est donc en #4100.
Hors en ajoutant #20, nous descendons normalement d'un bloc ligne complet. Sauf quand on atteind #40E0 puisqu'au lieu d'obtenir le bloc suivant, on obtient la ligne 2...

Regardons cela de plus pret.
Si le dernier bloc ligne du bloc commence en #40E0, en incrémentant 7 fois le poids fort pour arriver à la dernière ligne de ce bloc ligne, nous arrivons en #47E0.
On fait quelques tests et l'on constate assez facilement que l'adresse de la ligne d'en dessous est #4800...

La solution va donc être évidente.

Quand nous ajoutons #20 au poids faible de l'adresse et que celle-ci est en #47E0, nous obtenons donc #4700.
Ah bon ? J'avais pourtant dit #4800 juste au dessus... Oui effectivement, mais je parlais d'ajouter 32 à l'adresse complète.
Hors dans notre cas nous n'ajoutons 32 qu'au poids fort et non sans raison.

En ajoutant #20 à notre valeur #E0 du poids faible, il va de soit que celle-ci ne pourra de toute façon pas dépasser la valeur maximale d'un octet.
Hors en ajoutant #20 on dépasse #FF puisque le resultat serait #100.
Que se passe-t'il alors dans ce cas ?
Dans ce cas il se passe ce que l'on appelle un débordement.
La valeur ne pouvant être augmentée, celle-ci va boucler et le flag Carry sera mis (il signifiera qu'il y a une retenue).
Aussi notre valeur passera à 0 et le flag C sera mis. Ce qui va nous permettre de tester justement ce débordement en ajoutant une condition de saut.

Mais avant de faire ceci réfléchissons:
Nous l'avons vu, notre dernière adresse avant le débordement était #47E0.
Nous lançons notre routine de calcul.
La première instruction: INC D incrémente le poids fort. Nous passons donc à #48E0.
Après le AND on obtient 0, on continue donc le test.
On ajoute au poids faible. La carry est mise et celui-ci boucle à 0.
L'adresse est donc #4800. Exactement ce qu'il nous faut. On pourra s'arreter ici dans ce cas.

                ;DE contient l'adresse de la ligne
                        INC D           ;On incrémente le poids fort de l'adresse pour passer à la ligne suivante dans un bloc ligne
                        LD  A,D         ;On copie le poids fort dans A pour pouvoir opérer dessus sans le modifier
                        AND %00000111   ;On ne garde que les bits 0 à 2
                        RET NZ          ;Si le resultat n'est pas = 0 alors on ne sort pas d'un bloc ligne et on a la nouvelle adresse
                        LD  A,E         ;On récupère le poids faible de l'adresse
                        ADD A,32        ;On additionne 32 à A
                        LD  E,A         ;On recopie dans le poids faible de l'adresse
                        RET C           ;Si débordement on a fini

Voilà, notre débordement est géré, reste maintenant à corriger l'adresse.

Oui il faut la corriger. Car dans le cas ou la carry n'a pas été mise, la valeur obtenue ne sera pas bonne.

Pour le comprendre prennons par exemple l'adresse #47A0 (au hasard purement).
On lance notre routine de calcul:
On incrémente le poids fort. L'adresse vaut #48A0.
Après le AND on obtient 0, on continue donc le test.
On ajoute 32 au poids faible. L'adresse est donc #48C0 ce qui est complètement faut puisque nous envoie bien plus loin.

Mais quelle devrait être l'adresse de la ligne d'en dessous ?
Sous #47A0, la ligne a pour adresse #40C0.
En obtenant #48C0 nous avons donc 8 de trop dans le poids fort de l'adresse.
La solution ? Enlever 8 !

Enlever c'est soustraire. Cela tombe bien notre Z80 a des instructions de soustraction. En voici une par exemple qui va nous servir:
SUB data Instruction très simple puisqu'elle enlève la valeur data à l'accumulateur (A). Le resultat comme pour toute opération 8 bits sera dans A.

Ajoutons donc cette opération à notre calcul qui prendra donc sa forme définitive.

                ;DE contient l'adresse de la ligne
                        INC D           ;On incrémente le poids fort de l'adresse pour passer à la ligne suivante dans un bloc ligne
                        LD  A,D         ;On copie le poids fort dans A pour pouvoir opérer dessus sans le modifier
                        AND %00000111   ;On ne garde que les bits 0 à 2
                        RET NZ          ;Si le resultat n'est pas = 0 alors on ne sort pas d'un bloc ligne et on a la nouvelle adresse
                        LD  A,E         ;On récupère le poids faible de l'adresse
                        ADD A,32        ;On additionne 32 à A
                        LD  E,A         ;On recopie dans le poids faible de l'adresse
                        RET C           ;Si débordement on a fini
                        LD  A,D         ;On prend le poids fort
                        SUB 8           ;On lui enlève 8
                        LD  D,A         ;On met le résultat dans D
                        RET             ;Fin de la routine, DE contient l'adresse de la ligne inférieure quelque soit le cas

Comme nous avons pu le voir, faire une routine de calcul de la ligne inférieure sur ZX Spectrum n'est pas de tout repos.
Aussi je vous conseil fortement de garder cette routine dans un coin pour la réutiliser.
Nous avons vu dans ce cour quelques nouvelles instructions auquelles vous pourrez en ajouter d'autres en allant voir la documentation (par exemple pour les instructions de soustraction)

Pour le cour suivant, nous nous interesserons de plus pret à la façon donc l'adresse d'une ligne est gérée (ou pas de chance pour vous nous n'en avons pas terminé avec ça.).
Nous verrons comment avec un numéro de ligne nous pourrons trouver l'adresse de celle-ci à l'écran.
Le cour qui le suivra nous permettra d'ajouter un peu de couleur et surtout de savoir en fonction d'une adresse écran ou se trouve l'octet de couleur correspondant.

X

CONNEXION




Inscription