Assembleur Z80 sur RC2014 - épisode 2 - l'adressage
Bonsoir à tous,
Voici la suite des découvertes de l'assembleur Z80 sur RC2014 en utilisant SCM (Small Computer Monitor, voir sa configuration), il est possible de faire de l'assembleur directement sur le RC2014-Pro.
Nous continuons le visionnage des vidéos de Matt Heffernan relatif à la programmation Z80 sur Spectrum pour adapter les exemples au RC2014.
Episode 2: l'adressage
Le processeur Z80 n'est pas capable de traiter et modifier les informations directement dans la RAM. Par conséquent, Le Z80 utilise des registres dans lesquels il charge l'information pour la modifier avant de l'écrire à nouveau dans l'espace RAM.
Le chargement de donnée depuis la RAM dit "Load" en anglais (d'où l'opcode LD) est donc essentielle au fonctionnement des programmes. Cette instruction LD est permet de réaliser une assez grande variété de transferts différents (voir mode d'adressage).Cette seconde vidéo de Matt Heffenan traîte des différents modes d'adressage du Z80.
La mémoire sur le RC2014
Comme nous allons parler de transfert de données entre la mémoire RAM (ou ROM lors du Fetch d'instruction) et la mémoire du processeur (les registres).
C'est donc l'occasion idéale de revenir sur l'organisation mémoire du RC2014 avec la ROM88 (SCM, Basic, CP/M loader).
Espace d'adressage du RC2014
Espace d'adressage RC2014 |
La RAM est disponible à partir de l'adresse $8000. C'est à partir de cet adresse qu'il est possible de stocker du contenu.
Le Small Computer Monitor (SCM) est exécuté depuis la ROM (0x0000 à 0x3FFF). La ROM étant en lecture seule, il n'est pas possible d'en modifier le moindre octet.
J'utilise SCM pour assembler mes programmes à partir de $8000 et je stocke les données à partir de $8500.
Les registres du Z80
Il peut toujours être utile de rappeler la structure des registres du processeur.
L'accumulateur (A) intervient dans de nombreuses opérations arithmétique et permet de charger un octet depuis la mémoire RAM.
Par exemple: il n'est pas possible de charger une donnée de la RAM directement dans le registre B, il faut charger la donnée dans A puis transférer le registre A vers B.
A contrario, il est tout à fait possible de charger un mot (2 octets) directement dans les registres BC une donnée 16 bits en provenance de la RAM. C'est pareil pour DE et HL, Il peuvent charger des données 16 bits avec une instruction LD LH,($8005)... mais pas question de faire un LD H,($8005) pour charger uniquement 8 bits.
Une petite note sur les registres IX et IY qui servent d'indexes de référence pour charger de données depuis la mémoire en utilisant un offset par rapport à l'index mentionner ou en incrémentant la valeur d'index.
Les modes d'adressage Z80
S'il n'est pas utile de savoir identifier chacun des modes d'adressage, il est par contre utile de savoir qu'ils existent de pouvoir les exploiter.
S'il y a un tel détails dans les modes d'exécution c'est parce que cela implique parfois un nombre de cycles d'exécutions plus ou moins longs selon le mode utilité.
Voici les modes détaillés dans la vidéo:
- Mode d'adressage implicite - Implied Adress Mode
- Adressage de registres - Register adressing
- Adressage immédiat (8 bits) - Immediate addressing
- Adressage immédiat étendu (16 bit) - Extended immediate addressing
- Adressage indirect - indirect addressing
- modes d'adressage complémentaires abordés plus bas
Mode d'adresse implicite - Implied address mode
Il s'agit des opcodes (code opération) tenant sur un seul octet qui modifient la mémoire du microcontrôleur (les registres) durant leur exécution. Ces opcodes n'ont, par conséquent, pas d'opérande.
Ex:
- nop
- di
- ret
- rla
- halt
- ei
- exx
- ldir
Adressage de registre - register adressing
Lecture ou écriture d'une valeur d'un registre vers un autre. Ces opérations son codées sur un opcode d'un seul octet sans aucune opérande.
Donc pas de fetch complémentaire en mémoire ROM pour lire ces instructions, ni d'accès RAM.
L'exécution de ces instructions est extrêmement rapide.
- ld a,i
- ld b,c
- ld h,l
- ld r,a
- ld ixh, ixl ; 8 bits
- ld iyl, iyh
- ld sp,ix ; 16 bits
- ld sp,iy ; 16 bits
- ex af,af' ; échange des registres
- push bc ; 16 bits
- pop de
- in f,(c)
Adressage immédiat - immediate addressing
Ces opérations sont utilisées pour transférer une valeur 8bits (0 à 255, $00 à $FF) directement dans un registre.
L'opcode est accompagné d'un octet supplémentaire qui contient l'opérande (la valeur à charger). Ces opcode réclament le fetch d'un octet supplémentaire de la ROM (après le fetch de l'opcode).
Il n'y a pas de fetch supplémentaire en RAM après la capture de opérande en ROM.
- ld a,42 ; decimal
- ld b, %10001000 ; binaire
- ld c, $2A ; hexadécimal
- cp 123
Adressage immédiat étendu - extended immediate addressing
- ld bc, 4660 ; decimal
- ld de, %1000100010001000 ; binaire
- ld hl, $FFFF ; hexadecimal
- jp $89AB ; saut direct
Adressage indirect - indirect addressing
L'adressage indirecte est une instruction qui permet de lire ou écrire une valeur depuis la mémoire RAM.- ld a,($8500) ; lecture de 8bits depuis l'adresse $8500
- ld ($8505),a ; écriture du registre A à l'adresse $8505
- ld de, ($8600) ; lecture de 16bits depuis la mémoire. Ecriture dans les registres D & E.
- ld ($8602), hl ; écriture du contenu des registres H & L (16 bits) à l'adresse mémoire $8602 (et $8603)
- ld de, (hl) ; lecture de 16 bits à l'adresse mentionnée dans le registre HL.
Autres modes d'adressage
- Adressage en page zero - Zero page addressing
- Adressage relatif - Relative addressing
- Adressage par index - Index adressing
- Adressage de bits - Bit addressing
- Exécution sur flag - execution on specific status flag
- Mode d'interruption - interrupt mode
Adressage en page zero - Zero page addressing
Référence la première page mémoire de $0000 à $00FF.
Les instructions RS $00 à RST $FF sont codés sur un octet (donc extrêmement rapide en terme d'exécution) et permettent de faire des saut spécifiques depuis la page zero vers des routines spécifiques.
Dans le monde Z80, cela s'appelle les Vecteurs Reset (Reset Vector).
Dans le tuto sur le ZX Spectrum mentionne les Vecteurs Reset suivant:
- RST $00, RST $08, RST $10, RST $18
- RST $20, RST $28, RST $30, RST $38
Sur le RC2014, j'ai déjà identifié le vecteur suivant:
- RST $30 : appel d'API RC2014 offrant différents services comme l'affichage/saisie de caractère sur la console.
Adressage relatif - Relative addressing
L'adressage relatif permet de faire un saut lorsque l'offset d'adresse tient dans 8 bits (-128 à +127). Cela évite de devoir préciser une adresse complète sur 16 bits. Instruction plus courte = exécution plus rapide.
- jr nz,$28 ; jump relative if not Zero flag
- djnz $20
- jr z,$30 ; jump relative if Zero flag
Adressage par index - Index adressing
Utilise les registre d'adresse IX et IY et une offset de -128 à +127 pour effectuer une lecture en RAM.
- ld a,(IX+11)
- ld (IY-103),B
Adressage de bits - Bit addressing
Instruction permettant de tester ou modifier un bit de 0 à 7 dans un registre ou un octet en RAM.
- bit 1, a ; teste 2ieme bit du registre A
- set 3,(hl) ; mettre 4ieme bit a 1 à l'octet situe en RAM à l'adresse HL
- res 6,(IX+42) ; reset du 7ieme bit
Exécution sur flag - execution on specific status flag
permet d'exécuter une instruction de saut sur base d'une condition.
- jp c,$8899 ; jump if carry bit is set (C=1)
- ret nc ; return is no carry bit (C=0)
- call z,$8000 ; call if zero is set (Z=1)
- jp nz,$8010 ; call if non zero (Z=0)
- ret m ; return is sign bit set (S=1)
- call p,$9099 ; call if sign bit reset (S=0)
- jp pe,$8500 ; jump if overflow bit set (p/v=1)
- jp po,$8600 ; jump is overflow bit reset (p/v=0)
Mode d'interruption - interrupt mode
IM 0, IM 1, IM2 sont des instructions en un octet chacun. Cette instruction permet de modifier la façon dont le Z80 réagit à une interruption.
Noter: Small Computer Monitor (SCM) sur le RC2014 n'utilise pas d'interruption. Ce mécanisme reste donc disponible pour vos propres programmes.
- IM 0 ; spécifique aux périphériques (non exploiter sur Spectrum).
- IM 1 : appel le gestionnaire d'interruption en ROM à l'adresse $0038.
- IM 2 : utilise un "table vector" (Page=I) pour faire le saut, ce qui permet de définir le gestionnaire d'interruption dans le programme.
Tester l'adressage indirect
Dans l'exemple ci dessous, nous allons tester l'adressage indirect sur notre RC2014 avec Small Computer Monitor (SCM).
Qu'allons nous faire?
Nous allons préparer des données en RAM à l'adresse $8500.
Ensuite nous allons lire l'octet à l'adresse $8501 en utilisant l'adressage indirecte. Pour cela la valeur $8501 est stockée dans le registre HL puis ce dernier registre est utilisé pour charger la donnée dans l'accumulteur (registre A).
Nous vérifierons ensuite que la valeur attendue est bien présente dans l'accumulateur.
Etape 1: Préparer les données
Utiliser la commande "e 8500" pour y stocker une chaîne de caractère et quelques octets $00 .
*e 8500 8500: 5A Z LD E,D > "Z80 SCM is awesom 8511: 6F o LD L,A > 00 8512: 6D m LD L,L > 00 8513: 65 e LD H,L > 00 8514: 00 . NOP > 00 8515: 00 . NOP > 00 8516: 00 . NOP > . *
Ensuite on vérifie le contenu de la RAM avec le commande "m 8500".
*m 8500 8500: 5A 38 30 20 53 43 4D 20 69 73 20 61 77 65 73 6F Z80 SCM is aweso 8510: 6D 00 00 00 00 00 00 00 94 AD C1 E3 53 1D E5 6A m...........S..j 8520: B5 C3 71 BA 6E DC 62 5E 81 FC B6 BC D8 11 07 E5 ..q.n.b^........ 8530: D1 D0 22 C2 01 EC 21 18 6C 36 50 BA 44 5A 69 69 .."...!.l6P.DZii 8540: 01 B1 35 A9 B3 BE F4 D2 52 0F 64 52 E3 3B 9C 91 ..5.....R.dR.;.. 8550: 61 77 7E 99 DB 8A 9E 91 74 76 78 11 B3 26 46 C3 aw~.....tvx..&F. 8560: D5 57 1B 5C F1 C3 79 3E 14 38 F3 6D F6 EA 23 3D .W.\..y>.8.m..#= 8570: 41 33 79 0C 50 82 3C 4B B0 9A CC 12 80 7C 10 5A A3y.P.<K.....|.Z
On constate que l'octet à l'adresse $8501 est $38 (soit 56 en décimal), ce qui
correspond au caractère ASCII "8".
Les $00 encodés permettent de séparer nos données du contenu aléatoire de la RAM. C'est plus confortable à la lecture.
Etape 2: Effacer la mémoire
Cette fois, nous allons effacer la mémoire en $8000 avec la commande "fill" avant d'y assembler notre programme.
Ensuite, désassembler la mémoire en $8000 présente une série d'instruction NOP (no operation).
*fill 8000 8100 00 * *d 8000 8000: 00 . NOP 8001: 00 . NOP 8002: 00 . NOP 8003: 00 . NOP 8004: 00 . NOP 8005: 00 . NOP 8006: 00 . NOP 8007: 00 . NOP 8008: 00 . NOP 8009: 00 . NOP 800A: 00 . NOP 800B: 00 . NOP 800C: 00 . NOP
Etape 3: Saisir le code
Nous allons assembler le code suivant dans SCM
ld hl,$8501 ; Memoire à lire ld a,(hl) ; charger valeur de $8501 dans accumulateur ret ; retour au moniteur SCM
L'assemblage commence avec l'instruction "a 8000". L'assemblage es terminé avec un point "." .
*a 8000 8000: 00 . NOP > ld hl,$8501 8000: 21 01 85 !.. LD HL,$8501 8003: 00 . NOP > ld a,(hl) 8003: 7E ~ LD A,(HL) 8004: 00 . NOP > ret 8004: C9 . RET 8005: 00 . NOP > .
L'instruction RET est codée en $8004
Etape 4: contrôle des registres
Avant de démarrer le programme, nous allons utiliser l'instruction "r" pour inspecter les registres.
*r PC:8004 AF:614E BC:7BAE DE:6D84 HL:8500 IX:EB4A IY:EE39 Flags:-Z---PN- SP:FCBE AF'78A2 BC'423D DE'FCF6 HL'0C13 (S)0003 IR:D209 Flags'S-----N- *
On peut y voir que le registre "a" contient actuellement la valeur $61. Nous pourrons donc constater qu'elle à bien changée.
Etape 5: placer un point d'arrêt
De très nombreuses instructions modifient le contenu des registres. Il faut donc contrôler le contenu du registre A juste après l'exécution de "ld a,(hl)" et avant l'instruction "ret".
C'est là qu'intervient le point d'arrêt (dit Breakpoint en anglais) puisqu'il permet de s'arrêter avant l'exécution du RET.
*b 8004 Breakpoint set *
Etape 6: exécution et contrôle
Nous allons maintenant démarrer le programme (g 8000) qui s'arrêtera en $8004 (juste avant le RET).
*g 8000 Breakpoint PC:8004 AF:384E BC:7BAE DE:6D84 HL:8501 IX:EB4A IY:EE39 Flags:-Z---PN- *r PC:8004 AF:384E BC:7BAE DE:6D84 HL:8501 IX:EB4A IY:EE39 Flags:-Z---PN- SP:FCBE AF'78A2 BC'423D DE'FCF6 HL'0C13 (S)0003 IR:D235 Flags'S-----N- *
Dès le breakpoint activé, le moniteur affiche directement les registres principaux sont affichés.
En saisissant la commande "R", il sera possible de voir le contenu détaillé des registres.
Il est possible de constater la valeur $38 dans le registre A... comme cela est attendu (la lettre "8" du message "Z80 is awesom").
Note: l'assembleur aurait pu être réduit à une ligne ld a,($8501)
C'est pas fini!
La vidéo présente l'affichage d'une chaîne de caractères. Le présent article est bien assez long, je vais donc reporter l'écriture de ce programme assembleur aux deux articles suivants... parce qu'il faudra aussi parler d'appel d'API RC2014 (nous ne sommes pas sur un Spectrum!).
Écrire un commentaire