Programmation Assembleur Z80/Expert
Programmation niveau expert en Z80
modifierUtilisation avancée de pile
modifierAuto-actualisation de la pile
modifierAdmettons 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
modifierIl 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
modifierLe 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