Programmation Assembleur Z80/Expert

Programmation niveau expert en Z80

modifier

Utilisation avancée de pile

modifier

Auto-actualisation de la pile

modifier

Admettons qu'un programme ait besoin de lire une tables cyclique de longueur arbitraire. Le programme ci-dessous peut-être appelé n'importe quand, interruptions désactivées, il enregistre où il en est et se réinitialise tout seul.

matable
defw valeur1
defw valeur2
defw valeur3
lecture_table
gestionptr LD SP,matable
gestioncpt LD A,3
POP DE
DEC A
LD (gestioncpt+1),A
LD (gestionptr+1),SP
JR termine
LD SP,matable
LD (gestionptr+1),SP
LD A,3
LD (gestioncpt+1),A
termine

Ce type classique de gestion n'est déjà pas rapide, mais il présente le deuxième inconvénient de ne pas être en temps constant. Ainsi, lorsqu'on arrive en fin de table et que A vaut zéro, on doit recharger les valeurs d'initialisation de SP et de A puis les écrire à nouveau.

Si nous écrivons dans la table l'adresse de la prochaine valeur à lire, nous pouvons réaliser un automate qui rebouclera naturellement en fin de liste.

matable
defw valeur1
defw $+2      ; adresse de valeur2
defw valeur2
defw $+2      ; adresse de valeur3
defw valeur3
defw matable  ; rebouclage avec l'adresse de valeur1
lecture_table
gestionptr LD SP,matable
POP DE
POP HL
LD (gestionptr+1),HL ; actualisation de l'adresse de pile pour le prochain appel

Un système équivalent est utilisé dans la gestion des sons SID d'Arkos Tracker II car il fait gagner beaucoup de temps machine.

Utiliser plusieurs piles

modifier

Il est possible d'utiliser autant de piles que l'on veut avec le Z80, pour peu que le programme qui la modifie conserve l'ancienne valeur. Ainsi, un sous-programme pourra très bien utiliser la pile pour stocker plus rapidement des tableaux de données et les restituer de la même façon. N'oubliez pas qu'il faut toujours désactiver les interruptions quand on s'en sert de façon détournée. L'exemple ci-dessous part du principe que le calcul d'adresse et celui de couleur sont trop complexes pour être réalisés dans une seule boucle. Plutôt que de sauvegarder/restituer les registres, il apparait plus simple d'utiliser une table temporaire.

Ce genre de routine prend tout son sens dans le cas d'un page-flipping (ou tic-toc). Au premier appel on écrit dans le tableau1 pendant qu'on affiche tableau2. À l'appel suivant on fait l'inverse: On écrit dans tableau2 pendant que l'on lit tableau1. Cette technique est très utilisée dans le jeu vidéo ou les démonstrations techniques.

DI    ; désactive les interruptions
LD (sauvegarde_sp+1),SP
LD SP,montableau+200*2 ; se positionner en fin de tableau
LD B,200
boucle_ecriture
; calculs d'adresse par exemple dans HL
PUSH HL
DJNZ boucle_ecriture
; relecture
LD B,200
boucle_couleur
; calcul de couleur avec résultat dans A
POP HL
LD (HL),A
DJNZ boucle_couleur
sauvegarde_sp LD SP,#1234 ; valeur sera modifiée par le début du sous-programme
EI                        ; autorise à nouveau les interruptions
RET ; la pile ayant retrouvé sa valeur d'origine, on peut RET(ourner) d'où l'on vient

Auto-modification de code

modifier

Le Z80 étant un processeur dépourvu de mécanisme de protection mémoire, il est possible de modifier à la volée le code qu'on exécute sans craindre les effets de bord. La plupart des langages évolués disposent d'une zone mémoire pour les données et d'une zone mémoire pour le code mais en assembleur, il est possible d'intégrer les données au code.

Structure classique d'un programme qui copie la mémoire en plusieurs fois:

copyblock
LD HL,(source)
LD DE,(destination)
LD BC,256
LDIR
LD (source),HL
LD (destination),DE
RET
...
source      DEFW 0
destination DEFW 0

Non seulement les instructions de lecture d'un registre 16 bits en mémoire sont lentes, mais elles occupent autant de place qu'une lecture de valeur immédiate 16 bits. Il faut aussi réserver de la place dans une zone de données (4 octets de plus dans ce cas). On peut ré-écrire le code en faisant une lecture immédiate des registres et pour la sauvegarde, on ira modifier les instructions de lecture immédiate.

copyblock
source      LD HL,0
destination LD DE,0
copyblock_init ; point d'entrée pour appeler la routine avec de nouvelles valeurs
LD BC,256
LDIR
LD (source+1),HL
LD (destination+1),DE
RET

Bien entendu, pour réaliser ce genre de modification, il est impératif de connaitre l'opcode d'une instruction. Pour tous les registres généraux, cet opcode est presque toujours d'un octet suivi de la valeur immédiate. Pour les registres d'index IX et IY, cette valeur est de 2. Dans le doute, on peut utiliser l'adresse de fin d'instruction moins la taille de la valeur immédiate comme ci-dessous. De cette façon, on peut modifier le registre comme on veut, toutes les références à la valeur immédiate resteront correctes.

source LD IX,0
source_valeur=$-2
...
LD HL,1234
LD (source_valeur),HL

Optimisation de code

modifier

Optimisations pour la taille

modifier

Optimisations pour la vitesse

modifier