Programmation Assembleur Z80/Jeu d instructions

Convention de lecture pour le jeu d'instructions

modifier

Préambule

modifier

La 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

modifier
reg8   : 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

modifier

Il 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

modifier

Le 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

modifier

La 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

modifier

Dans 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

modifier

Cet 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

modifier

L'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

modifier

Le 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.

L'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

L'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

modifier

L'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

modifier

Pour 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

modifier
OR 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

Cette 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.

Cette 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!

Cette instruction est le pendant de l'instruction CPI mais avec une décrémentation de HL.

Cette instruction est le pendant de l'instruction CPIR mais avec une décrémentation de HL.

Instructions de saut non conditionnel

modifier

sauts inconditionnels sans retour

Cette 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.

Cette 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.

Cette 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.

L'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.

Cette 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

modifier

Les sauts conditionnels n'effectuent le saut que si la condition est respectée.

sauts sans retour

JR 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 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 label  ; décrémente B et saute si B est différent de zéro (ne modifie pas les flags)

Les 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 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

modifier

Il 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

modifier

Lorsqu'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 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

modifier
POP 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

modifier

Le 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

modifier

On peut tester, effacer ou positionner chaque bit individuellement d'un registre ou d'un octet en mémoire avec ces opérations.

L'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

L'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

L'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

modifier

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. 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

modifier

Pour 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

 

RR / RRA

modifier

RRC / RRCA

modifier

SLL (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.

RL / RLA

modifier

RLC / RLCA

modifier

Ce 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 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

modifier

Il 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

modifier

Les opérations logiques de base concernent uniquement le registre A. Il n'existe pas de version 16 bits de ces opérations.

Effectue 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

Effectue 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

Effectue 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

Effectue le complément à un du registre A (inverse tous les bits). Rappel de la logique booléenne

0 -> 1
1 -> 0

Le registre A devient A soustrait de zéro (inverse le signe mathématique).

Cette instruction met à 1 le flag C et remet à zéro les flags H et N. Les autres flags ne sont pas modifiés.

Cette 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

modifier

Cette 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

modifier

Cette 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é

Cette 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é

Cette 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)

Cette 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)

Cette 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)

Cette 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

modifier

Si 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.

Cette 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

Cette 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

Cette instruction est similaire à LDI, si ce n'est que les pointeurs mémoire HL et DE sont décrémentés.

Cette 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

modifier

Il 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.

Le 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

Cette 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.

Cette 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.

Cette 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.

Cette 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.


C'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

Cette 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

Instructions de gestion des interruptions

modifier

Cette instruction (Disable Interruptions) désactive toutes les interruptions masquables.

Cette 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

Cette 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.

Cette 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).

Cette 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.

Cette 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.

C'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

modifier

Oui, 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.