Ça y est, la nouvelle saison des Retro Programmers United for Obscure System a démarré. Et cette fois, c'est le PHC-25 qui est à l'honneur. Un ordinateur de Sanyo à base de Z80 (d'un clone plus précisément) et d'un MC6845 (... un clone aussi) et plutôt bien fourni en mémoire vive pour cette classe de machines : 16 ko de RAM et 6 ko pour la VRAM, accessible directement par le Z80 (avec des contraintes de timing si on veut éviter des parasites graphiques).
Bien entendu, comme toutes les machines concernées par cette game jam, le PHC-25 est un ordinateur qui n'a pas une grande logithèque, et pas non plus beaucoup d'activité ni de documentation. C'est le principe.
Pour cette Jam, je suis parti sur l'idée que le programme sera en assembleur. Certains vont certainement encore faire des miracles en BASIC, mais ce n'est pas ma tasse de thé, même si je l'ai enté par le passé. Avec la communauté des participants, nous nous sommes donc penchés sur la compréhension de la machine. Pas facile là encore car nous ne sommes pas nombreux à avoir un PHC-25, il faut donc continuellement passer par les bonnes âmes qui peuvent lancer nos tests.
Après quelques jours, j'ai commencé à mettre mes notes, un programme d'exemple et une manière de packager un programme assembleur sur gitlab. C'est principalement le résultat d'un travail de recherche collectif.
J'ai pu aussi corriger deux petits trucs dans MAME, qui reste loin d'être parfait pour le PHC-25, mais qui est un tout petit peu mieux qu'il y a deux semaines. La machine reste assez simple et peu exotique par rapport à d'autres que l'on a traitées auparavant.
IM 2
Lors de nos discussions et analyse, il est apparu que la ROM du PHC-25 lançait une ISR (Interrupt Service Routine) à chaque synchronisation verticale. C'est classique. Pendant cette ISR, la ROM fait les modifications en VRAM en fonction des modifications d'affichages demandées par le BASIC, cela pour éviter les parasites graphiques. Il y a aussi une lecture du clavier, la gestion du son,...
Mais aucune possibilité de reroutage. Là où l'on trouve dans d'autres machines un « hook » sur lequel on peut attacher son propre code, ici, il n'y a rien.
La proposition a été faite de passer le Z80 en IM 2, afin de gérer entièrement l'interruption. Cela fonctionne, et ce que je vais expliquer ici. À noter que l'IM 2 est une fonctionnalité du Z80 et que la grande partie de ce qui suit est valable pour d'autres machines similaires à base de Z80.
Interrupt Mode ?
Le Z80 peut fonctionner selon trois modes d'interruption. Le mode 0 est le mode par défaut. C'est un mode reliquat du 8080. Dans ce mode, lorsque l'interruption est reçue, le Z80 va lire l'instruction suivante qui va lui être présentée depuis un mécanisme externe. Généralement, c'est une instruction RST
qui est utilisée, afin de brancher à l'une des routines RESTART du Z80.
Mais la plupart, si ce n'est toutes, des machines familiales à base de Z80 utilisent le mode 1, qui est beaucoup plus simple. Dans ce mode, lorsque l'interruption est reçue, le Z80 branche tout simplement à l'adresse fixe 0x0038, à partir de laquelle on place l'ISR. C'est ce que fait le PHC-25. Mais comme son ISR n'offre pas de moyen d'extension, on est vite limité si on veut prendre les choses en main.
Le mode 2, ou IM 2, est un mode qui est normalement fait pour être utilisé avec un contrôleur d'interruption externe. Dans ce mode, l'interruption est suivie d'une valeur paire sur 8 bits. Cette valeur est utilisée comme index dans une table d'adresses d'ISRs. Le Z80 va lire l'adresse de l'ISR à partir de cette table, puis il va brancher à cette adresse.
Cependant, le PHC-25 n'a pas de contrôleur d'interruption externe. Ainsi, la valeur reçue peut être un peu n'importe quoi (et même pas forcément paire). L'astuce consiste donc à utiliser une table dont toutes les entrées pointent vers la même adresse. Comme l'index peut-être paire ou impaire, cela implique une contrainte supplémentaire : l'adresse de l'ISR doit être composée de deux octets identiques ($FEFE par exemple). Autre petit piège, comme la valeur 255 peut-être reçue, la table doit contenir 257 octets, et non pas 256 uniquement.
En pratique
Voici le code commenté pour mettre en place l'IM 2 sur le PHC-25 :
; Code pour l'assembleur SjASMPlus
org $c009 ; Adresse de départ du programme.
; On commence à $C009, car c'est l'adresse de départ
; avec le chargeur BASIC utilisé. Mais vous pouvez
; adapter.
start:
call install_im2 ; Le programme démarre par l'installation de l'IM 2
infinite_loop:
halt ; Attente de l'interruption
jp infinite_loop ; Puis boucle
; Installation de l'IM 2
install_im2:
di ; On commence par désactiver les interruptions
; Comme on va modifier la configuration,
; on ne veut pas qu'elles se déclenchent pendant ce temps
; Déplace la pile
; Peut-être optionnel, mais comme ça, on sait où elle est
; On ne pourra pas retourner dans la ROM, mais de toute façon
; elle ne fonctionnerait pas en IM2.
pop hl ; sauve l'adresse de retour de la routine
ld sp,$fffe ; place la pile à $FFFE
push hl ; Pousse l'adresse de retour sur la pile
; À présent, le RET peut retourner à l'endroit de l'appel
; Remplit la table des vecteurs d'interruption
ld hl,$fe00 ; La table démarre en $FE00
ld e,l
ld d,h
inc de ; Idiome de remplissage de mémoire
ld bc,257 ; De longueur 257 octets
ld a,$fd ; Contenant l'adresse $fdfd
ld (hl),a
ldir ; Remplissage de mémoire
; On place un saut vers l'ISR dans le vecteur d'interruption
ld hl,$fdfd ; L'adresse du vecteur
ld a,$c3 ; Instruction de saut (JP) vers xxxx
ld (hl),a
inc hl
ld de,isr ; L'adresse de l'ISR
ld (hl),e
inc hl
ld (hl),d ; Met l'adresse de l'ISR après l'instruction JP
; Place la table des vecteurs d'interruption ($FExx)
ld a,$fe
ld i,a ; Met l'adresse haute à $FE
im 2 ; Commute en Mode d'Interruption 2
; All set
ei ; Réactive les interruptions
ret
char:
db 32
; Routine de service d'interruption
; Appelée au début de la synchronisation verticale
; Affiche un caractère en haut à gauche de l'écran
; (en mode SCREEN 1)
isr:
ld hl,char
ld a,(hl)
inc (hl)
ld hl,$6000
ld (hl),a
ei
reti