Programmation Assembleur Z80/Jeu d instructions
Convention de lecture pour le jeu d'instructions
modifierPréambule
modifierLa description du jeu d'instructions suit un ordre logique dans un but d'apprentissage, il ne doit pas être vu comme une documentation technique brute. Ainsi, les premières instructions décrites sont les instructions de chargement mémoire/registre, puis les instructions de comparaison et celles de sauts conditionnels. Enfin, les autres instructions réalisant des opérations ou calculs sur les registres. Voici un exemple simple de programme Z80:
LD A,(variable1) ; charger dans A l'octet présent à l'adresse variable1 LD HL,variable2 ; charger dans HL l'adresse de variable2 CP A,(HL) ; comparer la valeur de A avec l'octet à l'adresse HL (soit variable2) JR Z,egalite ; si la soustraction de A par (HL) vaut zéro, c'est que les deux valeurs sont égales, alors on réalise le saut vers egalite
Abréviations pour les opérandes
modifierreg8 : registre 8 bits (sauf I ou R) reg16 : registre 16 bits (sauf PC,SP,IR) dep8 : déplacement relatif 8 bits (-128 à +127) n : valeur immédiate 8 bits nn : valeur immédiate 16 bits (reg16): valeur en mémoire indexée par un registre 16 bits (sauf PC,SP,IR) (nn) : valeur en mémoire indexée en absolu par une valeur 16 bits
L'utilisation des registres IX et IY en adressage peut toujours s'accompagner d'un déplacement de type dep8.
Valeurs littérales
modifierIl est important de signaler que les valeurs littérales exprimées ici représentent un consensus global entre assembleurs Z80 mais les assembleurs les plus anciens ne supportent pas toutes les syntaxes.
12345 ; valeur décimale #1234 ; valeur hexadécimale 0x1234 ; valeur hexadécimale &1234 ; valeur hexadécimale héritée du Basic Locomotive (déconseillée pour confusion avec l'opérateur AND de certains assembleurs) $1234 ; valeur hexadécimale héritée de Motorola (déconseillé pour éviter la confusion avec l'adresse courante qui est $) %01010 ; valeur binaire 0b1010 ; valeur binaire @4736 ; valeur octale $ ; adresse de l'instruction en cours (utilisé principalement pour des sauts relatifs)
Valeurs remarquables
modifierLe Z80 est conçu pour traiter des données 8 bits ou 16 bits. Afin d'appréhender plus facilement les retenues ou les bits susceptibles d'être décalées, il faut faire ses gammes avec les valeurs dîtes remarquables et les connaitre par cœur.
- L’intervalle des valeurs en 8 bits signé est de -128 à +127 (soit 256 valeurs avec le zéro)
- L'intervalle des valeurs en 8 bits non signé est de 0 à 255 (soit toujours 256 valeurs)
- L’intervalle des valeurs en 16 bits signé est de -32768 à +32767 (soit 65536 valeurs avec le zéro)
- L'intervalle des valeurs en 16 bits non signé est de 0 à 65535 (soit toujours 65536 valeurs)
Valeur de chaque bit
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Valeur | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Comprendre le principe de la pile
modifierLa plupart des microprocesseurs possèdent un mécanisme de pile pour enregistrer (empiler) des informations et les restituer à la demande. Cette pile est une structure de donnée basée sur le principe de dernier arrivé, premier sorti (en anglais LIFO pour last in, first out). Sur le Z80, c'est le registre SP qui indique l'adresse en mémoire vive du dernier élément empilé. Les instructions PUSH et POP, mais aussi CALL et RET effectuent des sauvegardes et des restitutions depuis la pile.
Dans un langage évolué, l'utilisation de la pile est transparent et géré par le compilateur. En assembleur, toute fonction appelée par un CALL doit logiquement se terminer par un RET. Si on sauvegarde un certain nombre de registre dans la pile (pour pouvoir les modifier par exemple), il est impératif d'en restituer le même nombre pour ne pas faire déborder la pile.
Quand les interruptions sont actives, le programme en cours peut-être interrompu à tout moment. À cet instant, le processeur enregistre l'adresse courante (comme avec un CALL) dans la pile et exécute le programme défini pour les interruptions. Afin de ne pas corrompre l'exécution du programme interrompu, le programme sous interruption doit enregistrer tous les registres qu'il va modifier. La méthode la plus simple est d'utiliser la pile. En fin de routine, le programme sous interruption restituent un à un les registres. Ré-active les interruptions puis RET(ourne) au programme qui a été interrompu.
Dans le chapitre sur la programmation avancée, nous verrons des exemples détournés de l'utilisation de la pile. Tant pour écrire dans la mémoire plus rapidement que pour lire des paquets de données. Cette utilisation demande une désactivation des interruptions et une maitrise parfaite de ce qu'on fait.
Les préfixes
modifierDans les années 80, on a beaucoup parlé de préfixes étendus car les assembleurs de l'époque n'ont jamais été développés pour prendre en compte toutes les instructions du Z80. Cette situation perdure encore aujourd'hui et les assembleurs supportant les instructions étendues sont toujours rares (support partiel pour Winape et support complet pour Rasm et Sjasm) mais ils existent!
Ces préfixes étaient utilisés pour "débloquer" certaines fonctionnalités soit-disant cachées du Z80. En fait parfaitement documentées par Zilog au moment où ces assembleurs ont été créés.
Ainsi, pour adresser les 8 bits de poids faible de IX il fallait écrire
DEFB #DD : LD L,5
Ce qui est équivalent à
LD XL,5
Bien entendu la deuxième syntaxe est recommandée mais il faudra utiliser un assembleur moderne tel que Rasm ou Sjasm.
À propos des flags
modifierCet ouvrage référence le comportement utile des flags après opération. Le comportement des bits 3 et 5 est beaucoup trop complexe pour être abordé dans un ouvrage de programmation. Ce comportement est néanmoins nécessaire à la programmation d'un émulateur Z80. Il faut savoir qu'il existe un registre interne au Z80, appelé MEMPTR ou WZ selon les documents disponibles et que ce registre est modifié par certaines instructions (incrémentation, recopie de BC ou PC, valeur de port ou de A, ...). Lorsque l'on utilise l'instruction BIT, les bits 11 et 13 de ce registre sont recopiés dans les bits 3 et 5 des flags. D'autres instructions réalisant des calculs recopient les bits de l'accumulateur dans ces fameux bits.
référence: https://baltazarstudios.com/webshare/A-Z80/memptr_eng.txt
Instructions de chargement de données
modifierLD
modifierL'instruction de base pour charger une valeur est LD (contraction de LOAD). Les instructions de chargement ne modifient jamais les flags.
On peut utiliser différente combinaisons de LD (cette liste est indicative)
LD reg8,n ; charger une valeur immédiate 8 bits dans le registre 8 bits LD reg8,reg8 ; charger reg8 dans reg8 LD A,(nn) ; charger la valeur stockée à l'adresse nn dans A LD A,(reg16) LD reg8,(HL) ;sauf XH,XL,YH,YL LD reg16,nn LD reg16,(nn) LD (nn),reg16 LD (nn),A LD (reg16),A LD (HL),reg8 ;sauf XH,XL,YH,YL
Il n'est pas possible de charger un registre 16 bits dans un autre mais on peut utiliser presque toutes les combinaisons possibles avec les registres 8 bits. Ainsi, pour charger HL dans BC:
LD H,B LD L,C
Il n'est pas possible d'effectuer des combinaisons de chargement LD reg8,reg8 entre les registres XH,XL et YH,YL et encore H,L car ils utilisent une racine commune d'opcode en interne. Les opcodes qui utilisent XH,XL,YH,YL sont préfixés par #DD ou #FD et suivis de l'opcode utilisé pour les registres H et L. Ce préfixe indique au Z80 de basculer sur les registres IX ou IY au lieu de HL.
Instructions d'échange de registres ou mémoire
modifierLe Z80 permet la permutation de certains registres, notamment pour accéder aux registres secondaires. Beaucoup de systèmes d'exploitation ont été conçus en n'utilisant qu'un seul des deux jeux de registre du Z80. Ainsi, l'utilisateur peut modifier les registres courants à loisir sans se soucier de les sauvegarder avant utilisation. Le système d'exploitation n'ayant qu'à exécuter un EXX pour retrouver "ses" registres.
EXX
modifierL'instruction EXX permute les registres BC, DE et HL avec les registres secondaires BC', DE' et HL'. L'instruction s'utilise sans paramètre. Pour rappel, les registres secondaires ne peuvent pas être accédés directement.
EXX
EX
modifierL'instruction EX sert à échanger deux registres, ou un registre avec une valeur en mémoire. Les combinaisons suivantes sont:
EX AF,AF' ; échange le registre AF avec le registre secondaire AF' EX HL,DE ; échange le registres HL avec le registre DE EX HL,(SP) ; échange le registre HL avec la dernière valeur stockée dans la pile EX IX,(SP) ; échange le registre IX avec la dernière valeur stockée dans la pile EX IY,(SP) ; échange le registre IY avec la dernière valeur stockée dans la pile
Instructions de comparaison
modifierCP
modifierL'instruction CP est en logique interne une soustraction du registre A qui ne met pas le registre A à jour. Seuls les flags sont positionnés en fonction du résultat de cette soustraction.
CP reg8 CP im8 CP (hl) CP (ix+d) CP (iy+d)
Interprétation des flags (avec N qui est reg8, im8 ou (reg16) )
- Si A=N alors Z=1
- Si A!=N alors Z=0
- Si A<N alors C=1
- Si A>=N alors C=0
- Si A-N<0 alors S=1 (bit 7 du résultat copié dans S)
- Si A-N>=0 alors S=0
alternatives à CP
modifierPour certaines comparaisons simples (-1, 0 ou 1) il est possible d'utiliser des instructions plus rapides dont l'usage premier n'est pas la comparaison
OR A ; comparer A à zéro, ne modifie pas le registre
DEC A ; Si A vaut 1 alors le flag Z sera positionné. Le registre A est modifié
INC A ; Si A vaut 255 alors le flag Z sera positionné. Le registre A est modifié
comparaison 16 bits
modifierOR A ; efface la retenue sans modifier le registre A SBC HL,DE ; Le registre HL est modifié par la soustraction avec retenue
Interprétation des flags
- Si HL=DE alors Z=1
- Si HL!=DE alors Z=0
- Si HL<DE alors C=1
- Si HL>=DE alors C=0
- Si HL-DE<0 alors S=1
- Si HL-DE>=0 alors S=0
CPI
modifierCette instruction est une instruction de comparaison groupée. L'instruction est équivalente au pseudo-code suivant:
CP (HL) ; compare le registre A avec la valeur à l'adresse HL INC HL ; incrémente HL DEC BC ; décrémente BC
Si BC=0 après exécution de l'instruction, alors le flag P/V est mis à 0.
CPIR
modifierCette instruction est une répétition de l'instruction CPI tant que BC est plus grand que zéro ou que le registre A est différente de la valeur pointée par HL. Par exemple, pour trouver la première occurrence de A en mémoire, on peut écrire.
LD A,#55 ; chercher la valeur #55 LD HL,montableau LD BC,1024 ; taille maximum du tableau CPIR DEC HL ; pour avoir l'adresse de l'occurrence, il faut décrémenter HL car il est incrémenté après chaque comparaison.
- Après exécution le flag P/V est à 0 si on n'a trouvé aucune valeur OU que la valeur est la dernière du tableau. Il faut donc la tester!
CPD
modifierCette instruction est le pendant de l'instruction CPI mais avec une décrémentation de HL.
CPDR
modifierCette instruction est le pendant de l'instruction CPIR mais avec une décrémentation de HL.
Instructions de saut non conditionnel
modifiersauts inconditionnels sans retour
JR
modifierCette instruction permet d'effectuer un saut relatif, en avant ou en arrière avec une amplitude 8 bits, soit de -128 à 127. Il n'existe pas de version 16 bits de ce saut relatif. Certains assembleurs pourront automatiquement le remplacer par un saut 16 bits absolu mais comme le saut ne sera plus relatif, le code ne pourra s'exécuter correctement que de l'emplacement mémoire prévu à l'assemblage. Les autres assembleurs afficheront une erreur de saut relatif trop long.
JR label
- Il existe des versions conditionnelles de ce saut
- Le saut relatif présente l'intérêt d'avoir un micro-code plus compact (2 octets au lieu de 3 pour JP) et de pouvoir charger le programme qui n'utilise que des sauts relatifs, n'importe où en mémoire.
JP
modifierCette instruction permet d'effectuer un saut absolu dans l'espace d'adressage 16 bits. On peut donner en argument un label ou des trois registres suivants: HL,IX,IY
JP label
Attention, contrairement à ce que la syntaxe suivante suggère, on utilise bien la valeur du registre et non celle pointée par le registre!
JP (HL) JP (IX) ; saute à l'adresse IX / Il n'est pas possible d'utiliser d'offset 8 bits avec cette instruction JP (IY) ; saute à l'adresse IY / Il n'est pas possible d'utiliser d'offset 8 bits avec cette instruction
- Il existe des versions conditionnelles du saut JP label
- Avec l'utilisation de saut absolu, l'adresse de chargement et d'exécution d'un programme ne peut pas être modifiée.
CALL
modifierCette instruction effectue un saut absolu dans l'espace d'adressage 16 bits. L'adresse de l'instruction suivante est enregistrée dans la pile. Ainsi le programme appelant peut revenir où il en était avec un RET.
- Il existe des versions conditionnelles de ce saut.
RET
modifierL'instruction RET (abréviation de return) effectue un saut à l'adresse lue et retirée du haut de la pile. L'adresse aura été placée auparavant sur la pile typiquement par une instruction d'appel à une sous-routine.
RET
- Il existe des versions conditionnelles de ce saut
sauts inconditionnels avec retour
Équivalentes à un appel de fonction, ces instructions de saut enregistrent dans la pile l'adresse d'appel. Ainsi, le programme appelé peut retourner à l'appelant quand il se termine.
RST
modifierCette instruction est un saut absolu spécifique qui ne peut s'utiliser qu'avec les adresses #00,#08,#10,#18,#20,#28,#30 ou #38. L'adresse de l'instruction suivante est enregistrée dans la pile. Ainsi le programme appelant peut revenir où il en était avec un RET.
- L'instruction RST est essentiellement utilisée pour des appels systèmes, celui-ci étant généralement situé en début de mémoire.
- L'instruction RST est l'instruction de saut la plus compacte et la plus rapide qui soit car elle ne prend qu'un seul octet.
Instructions de saut conditionnel
modifierLes sauts conditionnels n'effectuent le saut que si la condition est respectée.
sauts sans retour
JR
modifierJR C,label ; saute si retenue (C flag = 1) JR NC,label ; saute si pas de retenue (C flag = 0) JR Z,label ; saute si zéro (Z flag = 1) JR NZ,label ; saute si non zéro (Z flag = 0)
JP
modifierJP Z,label ; saute si zéro (Z flag = 1) JP NZ,label ; saute si non zéro (Z flag = 0) JP C,label ; saute si retenue (C flag = 1) JP NC,label ; saute si pas de retenue (C flag = 0) JP M,label ; saute si S flag = 1 JP P,label ; saute si S flag = 0 JP PE,label ; saute si P/V flag = 1 JP PO,label ; saute si P/V flag = 0
DJNZ
modifierDJNZ label ; décrémente B et saute si B est différent de zéro (ne modifie pas les flags)
RET
modifierLes instructions de retour de fonction sont des sauts sans retour.
RET Z ; retourne à l'appelant si zéro (Z flag = 1) RET NZ ; retourne si non zéro (Z flag = 0) RET C ; retourne si retenue (C flag = 1) RET NC ; retourne si pas de retenue (C flag = 0) RET M ; retourne si S flag = 1 RET P ; retourne si S flag = 0 RET PE ; retourne si P/V flag = 1 RET PO ; retourne si P/V flag = 0
sauts avec retour
CALL
modifierCALL Z,label ; saute si zéro (Z flag = 1) CALL NZ,label ; saute si non zéro (Z flag = 0) CALL C,label ; saute si retenue (C flag = 1) CALL NC,label ; saute si pas de retenue (C flag = 0) CALL M,label ; saute si S flag = 1 CALL P,label ; saute si S flag = 0 CALL PE,label ; saute si P/V flag = 1 CALL PO,label ; saute si P/V flag = 0
hack d'instruction: RST #38 conditionnel
modifierIl est possible de réaliser l'équivalent d'un RST Z,#38, d'un RST NZ,#38, d'un RST C,#38 ou enfin d'un RST NC,#38
En effet, l'opcode du RST #38 est #FF
On peut donc faire un RST Z,#38 (les syntaxes conditionnelles du RST #38 sont supportées par Rasm) en écrivant JR Z,$+1 Et par analogie, JR NZ,$+1 ou JR C,$+1 ou JR NC,$+1
Explications: - Si la condition n'est pas remplie, on passe à l'instruction suivante (en 2 nops ou 7 cycles) - Si la condition est remplie, on saute dans l'opcode lui même, sur la donnée qui permet de coder le saut relatif, ici #FF qui est l'opcode du RST #38
L'intérêt est évident, on est plus compact qu'un CALL Z,#38 et surtout beaucoup plus rapide!
Instructions de sauvegarde des registres dans la pile
modifierLorsqu'on appelle une sous-routine, on peut avoir besoin de sauvegarder certains registres. Cette sauvegarde peut se faire indifféremment avant l'appel ou après l'appel même si la logique académique veut que ce soit l'appelé qui protège les registres qu'il va modifier.
PUSH
modifierPUSH AF ; sauvegarde AF dans la pile pointée par SP et décrémente SP de 2 PUSH BC ; ... PUSH DE PUSH HL PUSH IX PUSH IY
POP l'ordre est inversé par rapport a push
modifierPOP IY POP IX POP HL POP DE POP BC POP AF ; récupère dans AF la valeur pointée par SP et incrémente SP de 2
Il n'est pas possible d'écrire ou de lire le registre de pile dans la pile. Mais on peut le faire de façon indirecte (voir chapitre programmation avancée).
LD (spsav+1),SP ; écrire la valeur de SP dans le micro-code de l'instruction ci-dessous spsav LD HL,#1234 ; la valeur #1234 aura été remplacée par la valeur de SP PUSH HL ; donc PUSH SP
POP HL ; récupère une valeur de la pile LD SP,HL ; et la copie dans SP, donc POP SP
Il existe des instructions pour le registre PC mais on leur a donné d'autres noms
CALL $+3 ; équivalent à un PUSH PC pour enregistrer dans la pile l'adresse courante RET ; équivalent à un POP PC
pour aller plus loin
modifierLe Z80 ayant un bus de donnée 8 bits, il ne peut réaliser l'écriture ou la lecture mémoire 16 bits qu'en deux fois. Lors du PUSH, on descend dans la mémoire c'est donc logiquement l'octet de poids faible qui est écrit en premier, suivi de l'octet de poids fort. À l'opposé, lors d'une lecture mémoire 16 bits avec le POP, c'est l'octet de poids fort qui est lu en premier, suivi de l'octet de poids faible. Ceci peut avoir une incidence sur le matériel qui décode des valeurs 16 bits.
Instructions de modification de bit
modifierOn peut tester, effacer ou positionner chaque bit individuellement d'un registre ou d'un octet en mémoire avec ces opérations.
SET
modifierL'instruction SET sert à positionner à 1 n'importe quel bit d'un registre ou d'un octet en mémoire.
SET n,reg8 ; positionner un bit (sauf XH,XL,YH,YL) SET n,(reg16) ; positionner un bit (sauf BC,DE)
- n est le numéro de bit à traiter, valeur de 0 à 7 avec 0 pour le bit le moins significatif.
- L'instruction ne modifie pas les flags
RES
modifierL'instruction RES (abréviation de reset) sert à effacer n'importe quel bit d'un registre ou d'un octet en mémoire.
RES n,reg8 ; effacer un bit (sauf XH,XL,YH,YL) RES n,(reg16) ; effacer un bit (sauf BC,DE)
- n est le numéro de bit à traiter, valeur de 0 à 7 avec 0 pour le bit le moins significatif.
- L'instruction ne modifie pas les flags
BIT
modifierL'instruction BIT sert à tester n'importe quel bit d'un registre ou d'un octet en mémoire. La valeur du bit est copié dans le flag Z, qui peut ensuite être utilisé dans une instruction conditionnelle.
BIT n,reg8 ; tester un bit (sauf XH,XL,YH,YL) BIT n,(reg16) ; tester un bit (sauf BC,DE)
- n est le numéro de bit à traiter, valeur de 0 à 7 avec 0 pour le bit le moins significatif.
- Si le bit testé est à zéro, alors le flag Z=1
- Si le bit testé est à un, alors le flag Z=0
super instruction avec copie du résultat dans un autre registre
modifierCette instruction possède un mode d'exécution qui copie le résultat dans un autre registre. La syntaxe a alors besoin de deux opérandes. Seuls les assembleurs Rasm et Sjasm les supportent.
Les combinaisons possibles de registres sont:
BIT n,(IX+dep8),A BIT n,(IX+dep8),B BIT n,(IX+dep8),C BIT n,(IX+dep8),D BIT n,(IX+dep8),E BIT n,(IX+dep8),H BIT n,(IX+dep8),L
On retrouve les mêmes combinaisons pour le registre IY, les instructions SET et RES.
Instructions de décalage et rotations de bit
modifierPour faciliter la navigation dans les instructions de décalage, voici un petit récapitulatif visuel des instructions de décalage les plus courantes.
De façon générale, toutes ces instructions modifient les flags de la façon suivante:
- flag S si le résultat est négatif (supérieur à 127)
- flag Z si le résultat est zéro
- flag P qui contient la parité des bits du résultat
- flag C qui contient le bit "sorti" du résultat
- flag H et N remis à zéro
SRL
modifierSRA
modifierRR / RRA
modifierRRC / RRCA
modifierSLL (SL1)
modifier- L'instruction SLL réalise un décalage de l'opérande vers la gauche d'un bit et injecte la valeur 1 dans le bit 0. On peut la trouver aussi sous la syntaxe SL1.
- Cette instruction fait partie des instructions qui ont été documentée plus tard par Zilog, c'est pourquoi peu d'assembleurs les supportent à part Rasm et Sjasm.
- Cette instruction possède un mode d'exécution qui copie le résultat dans un autre registre. La syntaxe a alors besoin de deux opérandes. Là encore, seuls Rasm et Sjasm les supportent.
Combinaisons possibles en mode simple:
SLL A SLL B SLL C SLL D SLL E SLL H SLL L SLL (HL) SLL (IX+dep8) SLL (IY+dep8)
Combinaisons possibles en mode groupé:
SLL (IX+dep8),A SLL (IX+dep8),B SLL (IX+dep8),C SLL (IX+dep8),D SLL (IX+dep8),E SLL (IX+dep8),H SLL (IX+dep8),L ; SLL (IY+dep8),A SLL (IY+dep8),B SLL (IY+dep8),C SLL (IY+dep8),D SLL (IY+dep8),E SLL (IY+dep8),H SLL (IY+dep8),L
- Note: Le registre destination contiendra la valeur du décalage même si l'adresse pointée par IX ou IY est en ROM. Dans ce cas de figure, la valeur est lue dans la ROM, le décalage est effectué dans un registre interne du Z80, puis il est copié dans le registre destination. Enfin, la valeur est écrite à l'adresse pointée par IX ou IY +dep8. Si l'adresse pointe sur une ROM active, alors la valeur sera écrite dans la mémoire située en arrière-plan de la ROM.
SLA
modifierRL / RLA
modifierRLC / RLCA
modifierRLD
modifierCe décalage très particulier impacte le registre A ainsi que l'adresse pointée par HL.
Il effectue une rotation de 4 bits vers la gauche sur le nombre 12 bits composé des 4 bits de poids faible de A et de (HL).
; exemple 'visuel' LD A,#12 LD (HL),#34 RLD ; A=#13 ; (HL)=#42
Les 4 bits supérieurs du registre A sont inchangés, il peut être nécessaire d'initialiser à zéro le registre avant usage.
Usage: Cette instruction trouve ses origines dans l'arithmétique BCD et permet par exemple de multiplier par 10 très facilement un nombre BCD de n'importe quelle taille. Comme elle modifie l'octet pointé par HL, il faut faire attention à son usage dans le cadre unique de la lecture.
Effet sur les flags:
- H et N à zéro
- P/V est la parité
- S et Z modifiés par définition
- C n'est pas modifié
RRD
modifierRRD est le pendant de l'instruction RLD. Il impacte le registre A ainsi que l'adresse pointée par HL.
Il effectue une rotation de 4 bits vers la droite sur le nombre 12 bits composé des 4 bits de poids faible de A et de (HL).
; exemple 'visuel' LD A,#12 LD (HL),#34 RRD ; A=#14 ; (HL)=#23
Effet sur les flags:
- H et N à zéro
- P/V est la parité
- S et Z modifiés par définition
- C n'est pas modifié
Décalage 16 bits
modifierIl est possible de réaliser des décalages 16 bits vers la gauche de façon native en utilisant l'addition.
ADD HL,HL ; copie le bit 15 dans C, décale tous les bits vers la gauche et injecte 0 dans le bit 0. ADC HL,HL ; copie le bit 15 dans C, décale tous les bits vers la gauche et injecte l'ancienne valeur de C dans le bit 0.
Pour faire un décalage équivalent à SLL il est nécessaire de décomposer
SCF ; force la carry C ADC HL,HL ; décalage avec injection de C dans le bit 0
ou bien
ADD HL,HL ; décalage des bits de HL SET 0,HL ; forcer le bit 0 de HL
On peut réaliser des décalages 16 bits sur les autres registres, toujours en décomposant (exemples pour le registre DE):
SLA DE devient
SLA E RL D
RL DE devient
RL E RL D
SLL DE devient
SLL E RL D
Instructions logiques de base
modifierLes opérations logiques de base concernent uniquement le registre A. Il n'existe pas de version 16 bits de ces opérations.
AND
modifierEffectue un ET logique sur le registre A.
AND A,reg8 AND A,im8 AND A,(reg16) ; sauf BC et DE
- C et N sont effacés
- flag H positionné à 1
- P/V est la parité de bits du résultat
- Z=1 si le résultat est nul
- S=1 si le résultat est négatif
Rappel de la logique booléenne
0 + 0 = 0 0 + 1 = 0 1 + 0 = 0 1 + 1 = 1
OR
modifierEffectue un OU logique sur le registre A.
OR A,reg8 OR A,im8 OR A,(reg16) ; sauf BC et DE
- C, H et N sont effacés
- P/V est la parité de bits du résultat
- Z=1 si le résultat est nul
- S=1 si le résultat est négatif
Rappel de la logique booléenne
0 + 0 = 0 0 + 1 = 1 1 + 0 = 1 1 + 1 = 1
XOR
modifierEffectue un OU exclusif logique sur le registre A.
XOR A,reg8 XOR A,im8 XOR A,(reg16) ; sauf BC et DE
- C, H et N sont effacés
- P/V est la parité de bits du résultat
- Z=1 si le résultat est nul
- S=1 si le résultat est négatif
Rappel de la logique booléenne
0 + 0 = 0 0 + 1 = 1 1 + 0 = 1 1 + 1 = 0
CPL
modifierEffectue le complément à un du registre A (inverse tous les bits). Rappel de la logique booléenne
0 -> 1 1 -> 0
NEG
modifierLe registre A devient A soustrait de zéro (inverse le signe mathématique).
SCF
modifierCette instruction met à 1 le flag C et remet à zéro les flags H et N. Les autres flags ne sont pas modifiés.
CCF
modifierCette instruction inverse les flags C et H. Le flag N est mis à zéro. Les autres flags ne sont pas modifiés.
Instruction de traitement des nombres en base 10
modifierDAA
modifierCette instruction sert à ajuster la valeur d'un octet quand on s'en sert comme si il contenait deux chiffres décimaux. Les 4 bits du haut et les 4 bits du bas sont supposés contenir chacun une valeur de 0 à 9. Ainsi, quand on réalise des additions ou des soustractions, on appelle l'instruction DAA pour corriger les retenues à effectuer sur le registre.
Le fonctionnement de l'instruction est le suivant:
- Si les 4 bits les moins significatifs de A contiennent un nombre plus grand que 9 ou que le flag H=1, alors on ajoute #06 au registre A.
- Si les 4 bits les plus significatifs de A sont aussi supérieurs à 9 ou que le flag C=1, alors on ajoute #60 au registre A et on positionne éventuellement la retenue pour le calcul suivant.
Quelques exemples d'exécution de l'instruction DAA:
LD A,valeur | ADD A,valeur | Résultat de l'addition | Valeur de A après DAA | flag C |
---|---|---|---|---|
#55 | #11 | #66 | #66 (55+11=66) | 0 |
#59 | #12 | #6B | #71 (59+12=71) | 0 |
#90 | #10 | #A0 | #00 (90+10=100) | 1 |
#99 | #01 | #9A | #00 (99+1=100) | 1 |
La retenue (flag C) est mise dès lors qu'on dépasse la valeur 99. Il faut la reporter sur l'octet BCD suivant.
Instructions mathématiques de base
modifierINC
modifierCette instruction incrémente le registre spécifié en opérande de 1.
INC reg8 ; n'importe quel registre 8 bits sauf I, R et F INC reg16 ; n'importe quel registre 16 bits sauf AF INC (HL) INC (IX+n) INC (IY+n)
Effet de l'incrémentation 8 bits sur les flags
- Le flag C n'est pas modifié!
- H=1, N=0
- P/V parité des bits du résultat
- Z est mis à 1 si le résultat est zéro
- S est mis à 1 si le résultat est négatif
Effet de l'incrémentation 16 bits sur les flags
- Aucun flag n'est modifié
DEC
modifierCette instruction décrémente le registre spécifié en opérande de 1.
DEC reg8 ; n'importe quel registre 8 bits sauf I, R et F DEC reg16 ; n'importe quel registre 16 bits sauf AF DEC (HL) DEC (IX+n) DEC (IY+n)
Effet de la décrémentation 8 bits sur les flags
- Le flag C n'est pas modifié!
- H=1, N=1
- P/V parité des bits du résultat
- Z est mis à 1 si le résultat est zéro
- S est mis à 1 si le résultat est négatif
Effet de la décrémentation 16 bits sur les flags
- Aucun flag n'est modifié
ADD
modifierCette instruction réalise une addition dont le résultat est stocké dans la première opérande. Les combinaisons possibles sont les suivantes:
ADD A,im8 ADD A,reg8 ADD A,(reg16) ADD HL,HL ou BC ou DE ou SP ADD IX,BC ou DE ou IX ou SP ADD IY,BC ou DE ou IY ou SP
Effet sur les flags en addition 8 bits
- flag N est mis à zéro
- P/V est interprété comme dépassement de donnée
- flag Z mis à 1 si le résultat est zéro
- flag C mis à 1 si le résultat déborde
- flag S mis à 1 si le résultat est négatif
Effet sur les flags en addition 16 bits
- flag H est indéfini
- P/V, S et Z ne sont pas modifiés
- flag C mis à 1 si le résultat déborde
Attention: Comme l'addition se fait en interprétant les nombres négatifs comme des nombres positifs, la notion de débordement est toute relative. Ainsi, soustraire un nombre plus petit à un autre (en additionnant -100 à 101 par exemple) va positionner la retenue tandis qu'additionner -100 à 99 ne la positionnera pas. Quand on transpose les valeurs négatives en nombres positifs, le positionnement des flags fait sens.
Note On peut écrire de façon raccourcie l'addition 8 bits sans mentionner le registre A
ADD 5 ; équivalent à ADD A,5 ADD B ; équivalent à ADD A,B ADD (HL); équivalent à ADD A,(HL)
SUB
modifierCette instruction réalise une soustraction dont le résultat est stocké dans la première opérande. Le registre destination est toujours A. Les combinaisons possibles sont les suivantes:
SUB A,im8 SUB A,reg8 SUB A,(reg16)
Effet sur les flags
- flag N est mis à 1
- P/V à 1 si dépassement
- flag C à 1 si retenue
- flag Z à 1 si le résultat est zéro
- flag S mis à 1 si le résultat est négatif
Note On peut écrire de façon raccourcie la soustraction 8 bits sans mentionner le registre A
SUB 5 ; équivalent à SUB A,5 SUB D ; équivalent à SUB A,D SUB (HL) ; équivalent à SUB A,(HL)
ADC
modifierCette instruction réalise une addition avec ajout de la retenue contenue dans le flag C. Le résultat est stocké dans la première opérande qui est soit A, soit HL. Les combinaisons possibles sont les suivantes:
ADC A,im8 ADC A,reg8 ADC A,(reg16) ; sauf BC,DE,SP ADC HL,BC ou DE ou HL ou SP
Effet sur les flags
- Le flag N est mis à zéro
- P/V est mis à 1 en cas de dépassement
- Le flag Z est mis à 1 si le résultat est nul
- Le flag C est mis à 1 en cas de retenue
- Le flag S est mis à 1 si le bit 15 est à 1 (valeur qui peut être considérée comme négative)
Note: On peut écrire de façon raccourcie l'addition 8 bits sans mentionner le registre A
ADC 5 ; équivalent à ADC A,5 ADC H ; équivalent à ADC A,H ADC (HL) ; équivalent à ADC A,(HL)
SBC
modifierCette instruction réalise une soustraction en tenant compte de la retenue contenue dans le flag C. Le résultat est stocké dans la première opérande qui est soit A, soit HL. Les combinaisons possibles sont:
SBC A,im8 SBC A,reg8 SBC A,(reg16) ; sauf BC,DE,SP SBC HL,BC ou DE ou HL ou SP
Effet sur les flags
- Le flag N est mis à 1
- P/V est mis à 1 en cas de dépassement
- Le flag Z est mis à 1 si le résultat est nul
- Le flag C est mis à 1 en cas de retenue
- Le flag S est mis à 1 si le bit 15 est à 1 (valeur qui peut être considérée comme négative)
Note: On peut écrire de façon raccourcie la soustraction 8 bits sans mentionner le registre A
SBC 5 ; équivalent à SBC A,5 SBC H ; équivalent à SBC A,H SBC (HL) ; équivalent à SBC A,(HL)
Instructions de copie mémoire
modifierSi il est possible de copier la mémoire à partir d'instructions simples, le Z80 offre plusieurs instructions de copie de blocs avec ou sans répétition. Ces super-instructions ont deux avantages:
- Le premier est de pouvoir utiliser BC comme compteur 16 bits et de tester si BC est égal à zéro pour la répétition.
- Le temps d'exécution de cette super instruction est deux fois plus rapide que de décomposer en instructions simples.
LDI
modifierCette instruction exécute la copie mémoire d'un octet de (HL) vers (DE), puis incrémente HL,DE et décrémente BC.
LDI
- Le flag P/V est mis à zéro quand BC=0
LDIR
modifierCette instruction exécute la copie mémoire de la même façon que LDI, tant que BC est supérieure à zéro.
LD HL,source LD DE,destination LD BC,nombre LDIR ; copie BC octets depuis HL vers DE
- Le flag P/V est mis à zéro quand l'opération LDIR est terminée
- HL et DE auront été incrémenté de BC
- BC vaut toujours zéro en fin d'exécution
Le pseudo-code de cette instruction est le suivant:
copymem LD A,(HL) LD (DE),A INC HL INC DE DEC BC JR NZ,copymem ; note:impossible à faire en vrai car DEC BC ne modifie pas les flags
LDD
modifierCette instruction est similaire à LDI, si ce n'est que les pointeurs mémoire HL et DE sont décrémentés.
LDDR
modifierCette instruction est similaire à LDIR, si ce n'est que les pointeurs mémoire HL et DE sont décrémentés.
Instructions d'entrée/sortie
modifierIl est possible de lire ou d'écrire sur un port mémoire. Cette section ne sera pas détaillée car les opérations d'entrée/sortie sont spécifiques au matériel avec lequel est connecté le Z80. Pour la petite anecdote, sur l'Amstrad CPC, les ingénieurs ont inversés les bits de poids fort avec les bits de poids faible du port d'entrée/sortie par économie, rendant inutilisables toutes les fonctions entrée/sortie de répétition qu'offre le Z80. Le processeur Z80 est conçu pour un design qui utilise le port pointé par le registre C alors qu'Amstrad a créé un design qui utilise le registre B pour pointer le port. Comme les instructions de répétition utilisent le registre B comme compteur, il faut pouvoir réajuster le registre B à chaque itération.
OUT
modifierLe OUT est l'instruction élémentaire pour envoyer une donnée sur un port I/O. Elle ne s'utilise qu'avec les registres 8 bits non spéciaux.
OUT (C),A ; envoyer la valeur du registre A sur le port C OUT (C),B OUT (C),C OUT (C),D OUT (C),E OUT (C),H OUT (C),L OUT (C),0 ; envoyer zéro sur le port C OUT (im8),A ; envoyer A sur le port im8
OUTI
modifierCette instruction est une instruction groupée d'envoi de donnée depuis le tableau pointé par HL avec une post-incrémentation.
OUTI ; décrementer B*, envoyer la valeur de (HL) sur le port C, incrémenter HL
Il est important de noter que le registre B est décrémenté AVANT d'envoyer la valeur de (HL) sur le port comme on peut le lire dans beaucoup d'ouvrages. L'ordre a une importance car comme sur l'Amstrad le port de sortie est le port B, si on veut envoyer une valeur sur le port B, il faut la pré-incrémenter!
- Le flag P/V est mis à zéro si BC vaut zéro après l'appel à OUTI.
OTIR
modifierCette instruction répète le OUTI tant que B est supérieur à zéro.
- Le flag P/V est mis à zéro en fin d'itération.
OUTD
modifierCette instruction est une instruction groupée d'envoi de donnée depuis le tableau pointé par HL avec une post-décrémentation.
OUTI ; décrementer B*, envoyer la valeur de (HL) sur le port C, décrémenter HL
Il est important de noter que le registre B est décrémenté AVANT d'envoyer la valeur de (HL) sur le port comme on peut le lire dans beaucoup d'ouvrages. L'ordre a une importance car comme sur l'Amstrad le port de sortie est le port B, si on veut envoyer une valeur sur le port B, il faut la pré-incrémenter!
- Le flag P/V est mis à zéro si BC vaut zéro après l'appel à OUTI.
OTDR
modifierCette instruction répète le OUTD tant que B est supérieur à zéro.
- Le flag P/V est mis à zéro en fin d'itération.
IN
modifierC'est l'instruction élémentaire pour lire une valeur sur le port I/O. Elle ne s'utilise qu'avec les registres 8 bits non spéciaux.
IN A,(C) IN B,(C) IN C,(C) IN D,(C) IN E,(C) IN H,(C) IN L,(C) IN 0,(C) IN A,(im8)
Les flags sont modifiés en fonction du résultat renvoyé:
- flag S si la valeur lue est négative
- flag Z si la veleur lue est zéro
- flag P si le total des bits de la valeur lue est pair
INI
modifierCette instruction est une instruction groupée de récupération de donnée vers le tableau pointé par HL avec une post-incrémentation.
INI ; récupérer la valeur du port (C) et la mettre dans (HL), décrémenter B, incrémenter HL, positionner les flags HPNC au besoin
- N=1
- flag Z positionné si B vaut zéro après décrémentation
INIR
modifierIND
modifierINDR
modifierInstructions de gestion des interruptions
modifierDI
modifierCette instruction (Disable Interruptions) désactive toutes les interruptions masquables.
EI
modifierCette instruction (Enable Interruptions) active le déclenchement des interruptions après l'instruction suivante. La précision à propos de l'instruction suivante est importante. En effet, lorsqu'une routine appelée par interruption se termine, il faut autoriser à nouveau les interruptions (sinon elles ne se déclencheront plus). Mais il est tout à fait possible que pendant le déroulement de la fonction sous interruption, une autre interruption soit survenue. L'interruption est donc en attente et se déclenchera dès lors que les interruptions seront à nouveau autorisées. Nous avons vu que le déclenchement d'une interruption saute à une adresse définie par le mode d'interruption ET que l'adresse interrompue est enregistrée dans la pile. Si l'autorisation des interruptions est immédiatement consécutive à l'instruction EI, il ne serait pas possible de revenir au programme interrompu avant qu'une interruption en attente se déclenche. La conséquence serait un empilement d'adresses de retour dans la pile et un écrasement progressif de la mémoire, jusqu'à ce que la pile déborde sur la routine sous interruption. C'est pour cette raison qu'après un EI, le Z80 exécute UNE instruction supplémentaire avant d'autoriser à nouveau les interruptions. L'usage classique en retour d'interruption est le suivant :
EI RET
IM
modifierCette instruction (Interruption Mode) permet de choisir le mode d'interruption du Z80 parmi 3 modes. Par défaut, le Z80 démarre en mode 1.
Mode 0
IM 0
Ce mode de fonctionnement indique que les interruptions sont générées par un matériel externe. Le Z80 exécute une instruction dont l'opcode aura été envoyé par le matériel sur le bus de données. En théorie, l'instruction de saut serait un RST. Ce mode a été très peu utilisé sur les ordinateurs.
Mode 1
IM 1
Fonctionnement par défaut du Z80, le processeur exécute un saut à l'adresse #38. Sur un Amstrad CPC, les interruptions sont générées par une puce vidéo tous les 300èmes de seconde (soit 52 lignes vidéo). Sur un ZX Spectrum, les interruptions sont aussi générées en corrélation avec le contrôleur vidéo mais tous les 50èmes de seconde (fréquence de rafraîchissement à 50 Hz) lors de la VBL (synchro verticale : Vertical BLanking). Sur les calculatrices TI, le nombre d'interruptions dépend apparemment de l'état de la batterie (autour d'une centaine par seconde).
Mode 2
IM 2
Le mode 2 est un mode d'interruption vectorisé. Le Z80 récupère un numéro d'interruption sur le bus de données avec lequel il forme l'octet de poids faible d'une adresse. L'octet de poids fort de l'adresse est contenu dans le registre I. À l'adresse (toujours paire) pointée par la valeur 16 bits I<int>, le Z80 va lire une adresse 16 bits à laquelle il va sauter.
HALT
modifierCette instruction boucle en continu jusqu'au déclenchement d'une interruption. On se sert du HALT pour se synchroniser de façon précise sur une interruption. Quand on n'utilise pas de HALT, le saut à l'interruption ne peut se faire qu'après la fin de l'exécution de l'instruction en cours. Si cette instruction est longue, le déclenchement de l'interruption est retardé de quelques cycles d'horloge, ce qui peut être rédhibitoire si on a besoin d'une grande précision (typiquement programmation au vol de la puce vidéo).
RETI
modifierCette instruction sert à acquitter les interruptions à des périphériques hardware en fin de routine.
Il est indispensable de précéder l'instruction avec EI pour que les interruptions puissent se déclencher à nouveau, sinon l'interruption de la carte matérielle restera masquée jusqu'au prochain EI.
En effet, le déclenchement d'une interruption (exceptée NMI) met le flag à 0 (les deux copies internes IFF1 et IFF2 du processeur), comme un appel explicite à l'instruction DI.
Un appel explicite à EI remet les deux copies IFF1 et IFF2 à 1, mais le processeur ne permettra une interruption qu'après l'instruction suivante (RETI dans ce cas) afin qu'une interruption n'empêche pas son exécution.
Dans le cas d'une NMI (Non Maskable Interrupt), seul le flag IFF1 est mis à 0. La routine de traitement de cette interruption n'est pas obligée d'utiliser l'instruction EI, car les instructions RETN et RETI copient la valeur de IFF2 dans IFF1.
RETN
modifierCette instruction sert à acquitter une interruption non masquable en fin de routine. Tant que cette instruction n'est pas exécutée, les interruptions non masquables sont en attente.
RETN ou RETI ? Les deux instructions sont équivalentes. Cependant, RETI est l'alias officiel reconnu par les périphériques pour détecter la fin de traitement d'une interruption. Quand un périphérique voit passer le code de l'instruction RETI sur le bus de données, il sait qu'il peut retenter de déclencher une interruption.
LD I,A
modifierC'est la seule instruction qui permet de modifier le registre I. Le registre I indique le poids fort de l'adresse de la table de saut à exécuter lors des interruptions en mode IM 2.
Instruction qui ne fait rien
modifierNOP
modifierOui, il existe une instruction qui ne fait... ...rien! C'est l'instruction NOP pour No OPeration ou pas d'opération. En fait cette instruction peut jouer un rôle quand on a besoin d'une synchronisation précise. On ajoute des NOP pour créer un délai. Cette instruction ne modifie aucun registre (autre que PC qui passe à l'instruction suivante) et aucun flag. Son opcode #00 est simple à retenir.
Il existe d'autres opcodes qui ne font rien. Ce sont les opcodes non utilisés par le Z80. Comme ils ne sont pas décodés, le Z80 ne fera rien non plus mais ce genre d'utilisation est à éviter, pour préserver la compatibilité ascendante de versions plus évoluées du Z80.