— Cette page est l'assemblage de nombreux articles issus de Quasar CPC et manque un peu d'homogénéïté. En outre, il faudra peut-être scinder cette page en deux ou trois au final.
Le but de cette rubrique est de poser toutes les bases nécessaires afin de se lancer dans la programmation en Assembleur Z80 sur CPC. Nous verrons ainsi ici tout ce qui concerne les instructions, les registres, les adressages, etc..
— Basé sur l'article publié dans Quasar CPC numéro 1, Initiation à l'Assembleur, par OffseT.
Chassez que ve jais pous vrendre au berceau1) ! Autrement dit, je considère que vous n'avez jamais vu quelque listing assembleur que ce soit et que vous n'en connaissez aucune commande ! Ok ? C'est parti !
Il est primordial que je commence par vous expliquer la commande LD
(prononcer comme Load). Vous devez savoir qu'en assembleur Z80, on ne travaille pas avec des variables mais avec des registres. La différence est que ceux-ci ne peuvent contenir que des entiers et que leur nombre est limité, leur capacité aussi. Ainsi, vous devez également faire la
différence entre les registres simples et les doubles.
Les simples ne peuvent contenir que des nombres compris entre 0 et 2552) alors que les doubles peuvent aller jusqu'à 655353). Mais là où ça se corse, c'est quand un registre double est formé par deux registres simples. Non, ne hurlez pas! Voici des exemples : prenons les registres simples B et C ; ils formeront le registre double BC. Et, comme je suis gentil, je vous donne la liste de “tous” les registres doubles existants : AF, BC, DE, HL, IX, IY, etc…
Pour ce premier cours nous ne nous servirons que de A et HL qui sont les plus utilisés. Revenons donc à LD
: si vous faites un LD A,10
cela correspond à un A=10
en BASIC. Simple non ? De même, LD HL,65535
mettra 65535 dans HL. Oui mais là c'est un peu plus complexe et il vaut mieux raisonner en hexadécimal4). Ainsi, 65535=&FFFF et on lit alors très simplement que H contient &FF
, tout comme L5). De même, si l'on met un LD HL,&01B3
; H=&01
et L=&B3
. Compris ? Parfait ! Maintenant que vous connaissez LD
par coeur, je vais vous fournir deux vecteurs6). L'un équivalent à “PRINT CHR$(A)”
, c'est le &BB5A
et l'autre équivalent à “LOCATE H,L”
, c'est le &BB75
. Non ! On ne se sert pas de LD
pour les exécuter mais de la commande CALL
. C'est très simple, il faut un “CALL adresse”
et ça marche ! Alors, voici un petit programme qui affiche le code ascii 65 aux coordonnées 1,1 :
ORG &5000 ;Implantation du prog en &5000 LD HL,&0101 ;H=1 et L=1 CALL &BB75 ;LOCATE H,L LD A,65 ;A=65 CALL &BB5A ;PRINT CHR$(A) RET ;Retour au Basic
Et pour ceusses qui n'ont pas ouvert la doc de leur assembleur, je dis même comment lancer cette routine : sous BASIC faites un CALL &5000
… évidemment, après avoir assemblé le programme…
— Basé sur l'article publié dans Quasar CPC numéro 2, Initiation à l'assembleur, par Zik.
Nous allons ici étudier en douceur le cas d'un tout petit programme qui a l'avantage de mettre en jeu deux éléments essentiels : les boucles et l'accès aux données.
Commençons donc par les boucles. On utilise l'instruction JP
(JumP) pour faire un saut dans un programme. Je m'explique : si, par exemple, on veut qu'un programme recommence au début quand son exécution est terminée, on place l'instruction JP DEBUT
en fin de programme ; DEBUT
étant un label placé au début du prog' dans notre exemple (DEBUT
ayant été pris au harsard).
Mais j'y pense, on ne vous a pas parlé des labels ! Bon, je vais vous expliquer. Un label est un mot (ou une lettre) quelconque qui est placé en face d'une commande donnée, il sert à ne pas avoir à préciser l'adresse mémoire où se trouve l'instruction, mais seulement préciser le nom donné au label concerné. Pour mieux vous illustrer cela, vous n'avez qu'à regarder le programme ci-dessous :
ORG &5000 LD HL,TEXTE BOUCLE LD A,(HL) CP 0 RET Z CALL &BB5A INC HL JP BOUCLE ; TEXTE DB "Bonjour !",0
En observant le programme, vous avez pu voir l'instruction RET Z
(eh oui !). Vous devez connaître RET
mais pas Z
(qu'est-ce qu'il va encore nous sortir !?). Ce fameux Z
signifie “si le flag Z est positionné tu fais l'instruction”. Le flag Z est modifié par certaines commandes dont CP
(qui signifie “ComPare” au registre A), ce qui veut dire en simplifiant (dans notre prog') : ComPare A à 0, si A=0 alors RETourne au BASIC, sinon continue.
Nous aborderons plus en détail la question des flags dans la section suivante.
Il me reste donc à vous parler des data. C'est le même principe qu'en BASIC, on réserve une zone mémoire pour y stocker des données, sauf que DATA
est remplacé par DB
7) (ou DW
8) pour les tableaux d'adresses) et que la commande READ
du BASIC est remplacée par des LD
. LD HL,TEXTE
fait pointer HL sur les données (ici le texte), on lit celles-ci grâce à LD A,(HL)
et on incrémente le pointeur (ici INC HL
) pour pointer sur la donnée suivante. Vous pouvez déduire que le texte doit être terminé par un 0 et que ce programme correspond au PRINT
du BASIC (zai failli oublier de le dire !).
Les flags sont des indicateurs9) positionnés automatiquement par certaines instructions du Z80 et qui permettent d'effectuer des opérations conditionnelles (des branchements, des calculs, etc..). Ils sont contenus dans le registre F qui se présente de la forme suivante :
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
S | Z | X | H | X | P/V | N | C |
Où les flags sont les suivants :
Flag | Description |
---|---|
S | flag de signe |
Z | flag de zéro |
H | flag de demi-Carry |
P/V | flag de parité/overflow |
N | flag Add/Substract |
C | flag de Carry |
Voyons maintenant comment s'utilisent ces petits drapeaux…
— Basé sur l'article publié dans Quasar CPC numéro 3, Initiation à l'assembleur, par Zik.
Nous allons parler des instructions de saut en mémoire paramétrées. Je vais tout d'abord faire un petit rappel sur les fameuses instructions JP
et CALL
dont on vous a déjà parlé. JP
(JumP) peut-être comparé à la commande GOTO
du BASIC, à la différence qu'après un GOTO
on doit préciser un numéro de ligne alors qu'après un JP
on doit spécifier le nom d'un label (qui en fait représente une adresse en mémoire). De même, l'ensemble CALL
…RET
est l'équivalent, en BASIC, du GOSUB
…RETURN
.
Comme je l'ai exprimé précédemment, ces trois instructions (JP
, CALL
et RET
pour ceux qui ne suivent pas) admettent des paramètres qui se substituent aux IF
…THEN
…ELSE
du BASIC. Les tests possibles sont limités, ceux-ci sont en fait réalisés à partir des bits du registres F (pour Flags).
Je vais donc vous dire quels sont les tests disponibles et ensuite voir quelles sont les principales commandes qui modifient ces flags et donc agissent sur ces tests.
Voici comment on effectue ces tests :
RET flag
JP flag,label
CALL flag,label
flag
peut être Z, NZ, C, NC, ou encore d'autres expression plus complexes et moins utiles que je ne traiterai pas dans cet article.
Commençons par Z (pour Zero). Ce flag indique si l'opération qui précède le test a donné un résultat nul (avec CP
, DEC
ou les opérateurs logiques par exemple). NZ (pour Non-Zero) est donc le test contraire. C (pour Carry) permet de tester s'il s'est produit un débordement lors de l'opération précédent le test. Je rappelle qu'un débordement s'effectue lorsqu'une instruction faire dépasser à un registre sa valeur maximale ou quand le registre devrait prendre une valeur négative ce qui est impossible (les valeurs maximales sont 255 pour les registres simples et 65535 pour les doubles, 0 est la valeur minimale). À titre d'exemple, la carry sera mise à 1 si l'on fait un LD A,1
suivi d'un SUB A,2
(A=A-2) et A contiendra alors 255 et non -110) : on dit que A a bouclé.
On peut utiliser cela par la comptabilisation sur les doigts des mains (10 en principe, sait-on jamais). On peut donc compter 0 au minimum et 10 au maximum. Donc si l'on doit compter un nombre supérieur à 10, on partira de 1 jusqu'à 10, puis o, retiendra qu'une dizaine a été décomptée (ce qui équivaut à mettre la Carry à 1 pour le Z80) et on repartira à 0, et si besoin est, on bouclera de nouveau (s'il y a plusieurs dizaines juqu'à parvenir au nombre escompté… vu comme ça, on n'a plus trop envie de compter sur ses doigts) ; on voit ici la supériorité inifie de l'homme sur la machine qui peut comptabiliser le nombre de dépassements (le nombre de dizaines) alors que le pauvre Z80 ne peut malgré toute sa bonne volonté signaler qu'un seul dépassement ! On devinera que NC indique qu'aucun débordement n'a eu lieu lors de l'opération précédente. On notera par ailleurs que certaines commandes mettent d'office la Carry à 1 ou à 0, c'est le cas par exemple des opérateurs logiques (mode à 0) ou de SCF
(Set Carry Flag) (mise à 1).
Vous voulez des exemples ? D'accord, voici un exemple de gestion du flag Z à partir d'un CP
:
Exemple1 LD A,R CP 162 JP Z,Label1 RET | Exemple2 LD A,R CP 162 RET NZ JP Label1 |
Ces deux exemples font exactement la même chose : ils lancent la routine LABEL1
si A est égal à 162 ; R étant un registre interne non contrôlable et que l'on peut considérer (à tort) comme un générateur de nombres aléatoires.
Voyons maintenant un test sur la Carry à partir d'un ADD
:
Exemple3 LD A,R ADD A,128 JP C,Label2 RET
En examinant cette routine, on s'aperçoit qu'elle appelle Label2
si R est supérieur à 127 (car 128+128 est égal à 256 et donc égal à 0 sur 8 bits, il y a débordement).
— Basé sur l'article publié dans Quasar CPC numéro 5, Initiation à l'assembleur, par Zik.
Le tableau ci-dessous indique les modifications que subissent les différents flags en fonctions des instructions de l'assembleur. Comme on me le fait remarquer, il figure les modifications des flags P/V et S dont nous n'avons pas détaillé le fonctionnement dans la section sur les flags pour des raisons obscures.
Ce tableau est très utile quand on veut effectuer un test après une instruction, ne sachant pas si elle modifie effectivement le flag Z ou autre. Les points d'interrogation de la colonne de gauche représentent soit une donnée, soit un registre 8 ou 16 bits, soit une valeur ou une adresse. Toujours dans cette colonne, ”(16 bits)
” indique un registre 16 bits (ça paraît logique) et ”AUTRE
” tout ce qui n'est pas précisé.
Pour le reste, les astérisques (*) indiquent que le flag en question est modifié, les points (.) qu'il ne l'est pas, et les “0” ou “1” qu'il est mis à 0 ou 1 (et toc !).
|
|
|
— Basé sur l'article publié dans Quasar CPC numéro 5, Perfectionnement à l'Assembleur, par OffseT.
Maintenant que les bases de l'assembleur sont posées, nous allons pour la toute première fois évoquer la notion d'optimisation. En effet, il est des instructions qu'il convient d'éviter car elle n'ont absolument aucun intérêt dans le cas général. Je vous propose ici de lister les plus évidentes.
Tout d'abord, je ne veux plus voir de LD A,0
! C'est la façon la plus bête de perdre du temps machine… En effet, XOR A
est beaucoup plus rapide et, de plus, il prend deux fois moins de place en RAM (un octet contre deux pour LD A,0
). Pour ceux qui ne voient pas pourquoi XOR A
équivaut à LD A,0
, voici l'explication : comme chacun sait, XOR
est le OU exclusif dont voici la table de vérité :
XOR | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
Pour plus de clarté (il faut penser à ceux qui n'ont jamais utilisé le binaire) voici un petit exemples :
Comme vous pouvez le constater, il est indispensable de raisonner en binaire pour percevoir les subtilités de l'assembleur. Si vous avez bien suivi ce qui précède, vous devriez comprendre sans problème pourquoi un XOR A
met A à zéro… En outre XOR
a l'avantage de mettre la carry à 1 ce qui est fort utile dans certains cas. Les seuls cas où l'usage du LD A,0
se justifie, c'est lorsque l'on souhaite ne pas modifier les flags.
Et puis il y a aussi le fameux CP 0
; pour ainsi dire c'est encore plus “grave” que le LD A,0
puisque cette instruction est souvent utilisée dans des boucles d'où, catastrophe ! Le substitut idéal c'est le OR A
qui met le flag Z à zéro si A est nul. Je ne vais pas vous faire l'affront de détailler la table de vérité… Passons…
Sachez tout de même que, tout comme l'opérateur logique XOR
, OR
(et AND
aussi d'ailleurs) met la carry à 0.
— Basé sur l'article publié dans Quasar CPC numéro 5, Initiation à l'assembleur, par Zik.
Mais il y a un autre tableau dans ces pages (ô miracle) ! Celui-ci (qui ne s'est d'ailleurs pas saisi tout seul et instantanément) nous montre comment s'effectue le codage en mémoire de toutes les mnémoniques (légales) de l'assembleur.
Quelques explications s'imposent :
data
est une donnée sur 8 bits,data16
est une donnée 16 bits,addr
est une adresse 16 bits,disp
est un offset signé sur 8 bits,reg
est un registre 8 bits,rp
est un registre 16 bits11),b
est un numéro de bit entre 0 et 7,yy
est une donnée 8 bits,ll,hh
est une donnée 16 bits stockée en little endian (attention, elle est codée à l'envers : poids faible et poids fort ensuite),xx
représente un coupe de bits,
Voilà, j'ai pratiquement tout dit (bon, d'accord, je n'ai pas dit que toutes les valeurs numériques sont en hexadécimal ou en binaire, que ”port
” est un port (!) sur 8 bits et que ”m
” peut prendre les valeurs 0, 1 ou 2).
Cette liste de commandes contient toutes celles offertes par le Z80 lui-même mais sur CPC, compte tenu du câblage de celui-ci, IND
, INDR
, INI
, INIR
, OTDR
, OTIR
, OUTD
, OUTI
et RETN
ne fonctionnent pas correctement.
|
|
— Basé sur l'article publié dans Quasar CPC numéro 12, Assembleur : Software, par Zik.
Nous allons ici parler des commandes du Z80 qui sont mal connues ou plutôt “inconnues” ! En effet, elles ne sont mentionnées nulle part dans les documentations officielles sur le Z80 que j'ai eu et peu de désassembleurs les interprêtent toutes bien. Mon but est aussi de compléter le tableau ci-dessus.
La “découverte” intuitive de ces commandes provient d'une remarque sur les codes machines (que vous relèverez sur le tableau sus-mentionnée). Si vous regardez les codes des commandes concernant IX et IY par rapport aux équivalents sur HL, vous remarquerez que le code est le même avec comme préfixe l'octet &DD
pour IX et &FD
pour IY. Vite un exemple :
INC HL
est codé par &23
INC IX
est codé par &DD
,&23
INC IY
aura pour code &FD
, &23
Remarquable non ?! Bon soit… ce qui est intéressant c'est qu'il existe des commandes comme le LD H,12
, RL H
, etc. qui donc font des opérations 8 bits et les codes de ces commandes avec les préfixes 16) ne sont pas affectées à d'autres commandes. Et en fait, certaines de ces commandes non mentionnées marchent… mais pas toutes !
En clair, on peut grâce à ces commandes utiliser les registres IX et IY comme des paires de registres 8 bits un peu comme quand on scinde HL en H et L (traités séparément donc).
Voici l'additif au tableau précédent avec en plus le temps machine des commandes. J'ai pris comme convention d'écriture IXH pour l'octet de poids fort de IX et IXL pour le poids faible de IX. Le temps machine est exprimé en nombre de NOP
équivalent.
2 | ADC A,IXH | DB &DD:ADC A,H |
2 | ADD A,IXL | DB &DD:ADD A,H |
4 | ADD IX,IX | DB &DD:ADD HL,HL |
2 | AND IXH | DB &DD:AND H |
2 | CP IXH | DB &DD:CP H |
2 | DEC IXH | DB &DD:DEC H |
2 | INC IXH | DB &DD:INC H |
2 | LD reg,IXH | DB &DD:LD reg,H |
2 | LD IXH,reg | DB &DD:LD H,reg |
3 | LD IXH,data | DB &DD:LD H,data |
2 | OR IXH | DB &DD:OR H |
2 | SBC A,IXH | DB &DD:SBC A,H |
2 | SUB IXH | DB &DD:SUB H |
2 | XOR IXH | DB &DD:XOR H |
La colonne de droite indique ce que vous devez taper dans votre source. Pour avoir les mêmes commandes en IXL il suffit de remplacer H par L, toujours dans la colonne de droite. Comme je l'ai déjà dit, pour utiliser IY à la place de IX, il faut changer le DB &DD
en DB &FD
.
Je vous ferai remarquer que ADD HL,IX
et ADD IX,HL
n'existent pas, par contre on a bien ADD IX,IX
. J'espère que je n'ai pas oublié de commandes dans ce tableau, sinon je compte sur vous pour le corriger !
Au niveau désassemblage, le Hacker (du moins la version 4.81) connait toutes ces commandes, vous pouvez lui faire confiance à 100% ; The Insider comprend les LD
(et encore, il fait quelques erreurs) ; DAMS n'a pas l'air d'y comprendre grand chose (il met des astérisques !) et Maxam ne connait pas (il met des points d'interrogation, chacun son style !). Sachez pour finir que vous pouvez parsemer vos sources de &DD
ou &FD
, quand ça ne forme pas les commandes ci-dessous le Z80 les ignore (moyennant 1µs).
— Basé sur l'article publié dans Quasar CPC numéro 14, Assembleur : Software, par Zik.
Saviez-vous que le Z80 marche à pile ?! Très certainement puisque nous avons déjà parlé du registre SP précédemment. Il s'agit justement (quel hasard !) du pointeur de pile dit “Stack Pointer” (d'après mon dictionnaire ça se traduirait plutot par “index de tas”…).
Tout d'abord, je vous remercie de me poser cette question. Une pile permet le stockage temporaire de données en mémoire de manière simple.
Son emploi consiste donc en fait en la sauvegarde d'un registre (16 bits sur Z80). C'est ainsi que PC (le Program Counter, qui contient à chaque instant l'adresse de l'instruction exécutée) est sauvée dans la pile au moment d'un CALL
ou d'un RST
pour permettre de retrouver la bonne adresse à la rencontre d'un RET
.
Maintenant qu'on connait l'utilité de la pile, il est temps de s'intéresser à son fonctionnement (mais si !).
La pile du Z80 est de type LIFO (pour Last In, First Out). C'est-à-dire, en bon français, que le dernier éléments que vous y placez sera le premier à en sortir (sauf bidouille).
C'est là que je sors la bonne vieille comparaison avec une pile d'assiettes (j'ai dit “vieille” et pas “poussiéreuse”, la vaisselle est irréprochable chez moi). Donc, dans votre pile d'assiettes, celle qui se trouve en dessous est la première que vous avez empilée et, quand vous récupérez une assiette, vous prenez naturellement celle du dessus qui est bien la dernière mise dans la pile.
Voilà pour le principe, maintenant voyons plus concrètement comment cela est réalisé sur Z80…
PUSH
POP
Les opérations sont décrites dans l'ordre chronologique?. Si leur ordre était inversé, la pile créée serait tout aussi fonctionnelle. M'enfin bon, Zilog a choisi la première méthode.
Bien sûr, les octets de poids faible et fort sont inversés lors de la lecture ou de l'écriture en mémoire par la pile. Voici maintenant un petit exemple pour résumer tout ça :
La trace du petit programme ci-dessous aboutirait aux informations en vis-à-vis (ainsi qu'à un superbe plantage de l'ordinateur…).
; Petit programme d'exemple LD SP,&C000 LD BC,&BB5A LD DE,&568D LD HL,&ABCD PUSH DE ; (1) PUSH BC ; (2) POP HL ; (3) |
État de la mémoire
|
État des registres
|
La liste des instructions ayant un rapport avec SP n'est pas très étendue mais quelques unes d'entre elles sont très puissantes et relativement rapides. C'est ainsi que l'on trouve des instructions de chargement registre 16 bits vers SP qui sont quand même remarquables sur un processeur 8 bits ! Voici donc cette liste, les chiffres indiquent le temps machine pris en nombre de NOP
équivalent. Quand deux chiffres sont spécifiés ils correspondent au cas où la condition est vérifiée ou non.
ADC HL,SP ................ 4 ADD HL,SP ................ 3 ADD IX,SP ................ 4 ADD IY,SP ................ 4 CALL condition,adresse ... 5/3 DEC SP ................... 2 EX (SP),HL ............... 6 EX (SP),IX ............... 7 EX (SP),IY ............... 7 INC SP ................... 2 LD (adresse),SP .......... 6 LD SP,(adresse) .......... 6 | LD SP,HL ................. 2 LD SP,IX ................. 3 LD SP,IY ................. 3 LD SP,adresse ............ 3 POP AF ................... 3 POP BC ................... 3 POP DE ................... 3 POP HL ................... 3 POP IX ................... 4 POP IY ................... 4 | PUSH AF .................. 4 PUSH BC .................. 4 PUSH DE .................. 4 PUSH HL .................. 4 PUSH IX .................. 5 PUSH IY .................. 5 RET ...................... 3 RET condition ............ 4/2 RETI ..................... 4 RETN ..................... 4 RST adresse .............. 4 SBC HL,SP ................ 4 |
Comme vous pouvez le constater, l'intervention de la pile n'est pas toujours explicite. Concernant CALL
et RET
, la pile n'est modifiée que si la condition est vérifiée ; c'est-à-dire si le saut en mémoire est effectué.
La pile permet des transferts mémoire/registre très rapides, tout ça en décalant automatiquement le pointeur. D'où la tentation de détourner l'usage classique de la pile le temps d'exécuter notre routine adorée le plus rapidement possible.
Mais détourner la pile ne peut se faire qu'en prenant certaines précautions, sinon attention à la casse (cf. les assiettes de tout à l'heure).
Premièrement, il faut se méfier des interruptions qui modifient fatalement la pile. C'est pour cette raison que la plupart des routines qui détournent la pile interdisent les interruptions par un DI
.
Ensuite, le fait d'utiliser la pile oblige souvent à se passer de CALL
, RST
et de la sauvegarde des registres par PUSH
/POP
.
Enfin, si vous espérez voir votre programme rendre la main au système sans protestation vive de ce dernier (plantages divers), n'oubliez pas de sauvegardez la pile en début de programme pour pouvoir la restituer en fin.
Si vous respectez ces quelques règles, il n'y aura en principe pas de problème. Donc, avec la pile vous ne perdrez pas la face (ça y est, je l'ai placée !).
Le premier programme est plutôt là pour montrer une technique car au niveau des performances il n'égale pas une classique routine d'affichage construite autour d'un LDIR
. Cela malgré certains préjugés qu'on ne prend pas même la peine de vérifier (cf. ACPC 47 p.18 à 21) !
Bref, la pile est ici utilisée comme pointeur sur les données du sprite alors que HL adresse l'écran. Le POP
récupère deux octets du sprite (avec une rapidité remarquable). Ensuite, on place ces deux octets à l'écran, c'est là que cette routine perd tout son intérêt en ce qui concerne son temps d'exécution.
Le reste n'est que calcul pour obtenir l'adresse écran de la ligne suivante (l'équivalent d'un CALL &BC26
en somme). Remarque bien qu'un CALL
ne fonctionnerait pas (il déteriorerait les données du sprite).
; Affichage de sprite à la pile Org &8000 Nolist ; pour Maxam Ecran Equ &c050 Sprite Equ &a500 Hauteur Equ 50 Largeur Equ 18 ; la largeur doit être paire di ld (save_sp+1),sp ld sp,sprite ld hl,ecran ld b,hauteur Ligne ld c,largeur/2 Colonne pop de ld (hl),e inc hl ld (hl),d inc hl dec c jr nz,colonne ld de,&800-largeur add hl,de jr nc,suite ld de,&c050 add hl,de Suite djnz ligne Save_sp ld sp,0 ei ret
Le deuxième exemple d'utilisation est un peu l'inverse du premier. SP pointe ici sur l'écran et HL contient ce qui doit être affiché (des 0 pour un effaçage !). Chaque PUSH
place deux octets, pour un écran de 16384 octets on a donc besoin de 8192 PUSH
. Or 8192=256*32 donc le compte est bon ! Le ”CLS
” généré par cette routine est presque trois fois plus rapide que le meme fait par LDIR
. Une routine similaire était parue dans un numéro d'Amstrad Cent Pour Cent, mais cela fait déjà 8 ans de ça ! Ben mon vieux !
; Effaçage d'écran à la pile Org &4000 Nolist ; pour Maxam di ld (save_sp+1),sp ld sp,0 ld hl,0 ld b,0 Boucle push hl:push hl ; y'en a 32 push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl push hl:push hl djnz boucle Save_sp ld sp,0 ei ret
— Basé sur l'article publié dans Quasar CPC numéro 17, Assembleur : Software, par Zik.
La diversité est source de richesse donc, après avoir vu toutes les petits choses qui précèdent, nous allons nous intéresser aux instructions de contrôle et de test de bit du Z80 !
Les trois instructions en question sont BIT
, SET
et RES
. Elles nécessitent deux arguments qui sont le numéro du bit concerné par l'opération et l'argument sur lequel elle a lieu. Celui-ci désigne forcément une valeur 8 bits contenue soit dans un registre 8 bits soit en mémoire à l'adresse pointée par HL ou par un des registres d'index (IX ou IY). Le numéro de bit spécifié est donc un nombre de 0 à 7. Le zéro étant le poids faible comme d'habitude.
Les instructions RES
et SET
permettent de mettre le bit souhaité respectivement à 0 ou à 1. Voici un petit exemple : si HL contient &1234
et que la mémoire à l'adresse &1234
vaut &1B
, alors après l'exécution de SET 6,(HL)
, la valeur en mémoire en &1234 est &5B
et aucun registre n'a été modifié.
Par ailleurs, ces deux instructions ne modifier aucun flag contrairement aux opérations logiques (AND
, OR
et XOR
) qui peuvent elles aussi forcer des valeurs de bits (mais seulement sur A).
L'instruction BIT
permet de tester l'état d'un bit donné d'un registre (8 bits) ou en mémoire. Le résultat du test est donné par le flag Z qui est mis si le bit considéré vaut 0. Mais BIT
modifie en fait tous les flags sauf la Carry comme suit :
Flag | État après l'instruction BIT | Description |
---|---|---|
S | indéterminé | flag de signe |
Z | mis à 1 si le bit spécifié vaut 0, mis à 0 sinon | flag de zéro |
H | mis à un 1 | flag de demi-Carry |
P/V | indéterminé | flag de parité/overflow |
N | mis à 0 | flag Add/Substract |
C | inchangé | flag de Carry |
Les flags Add/Substract et de demi-Carry ne sont pas consultables directement, il faut aller voir dans le registre F qui est consitué de la manière suivante :
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
S | Z | X | H | X | P/V | N | C |
Je ne donnerai pas ici plus de détails sur les flags. Finissons plutôt par une illustration de l'instruction BIT :
ld ix,&1000 ld (ix-16),&3f bit 4,(ix-16) jp nz,toujours Jamais ...
Instruction n,r :
|
Instruction n,(IX+d) :
|
Instruction n,(IY+d) :
|
Où :
|
|
|
|
Pour :
BIT n,argument RES n,argument SET n,argument
On a :
argument | BIT | RES/SET |
---|---|---|
reg 8 bits | 2µs | 2µs |
(HL) | 3µs | 4µs |
(IX+d) | 6µs | 7µs |
(IY+d) | 6µs | 7µs |
Où : reg 8 bits = A, B, C, D, E, H, L
&FF
=255DEFB
avec les vieux assembleurs&FD