Comble de l'originalité, c'est bel et bien une foule d'informations concernant le fonctionnement des interruptions du Z80 et leur mise en œuvre sur CPC et CPC+ que vous allez trouver sur cette page.
La maîtrise des interruptions est un incontournable pour arriver à faire des programmes un peu élaborés, notamment sur CPC+ où la gestions des DMA et de l'interruption raster1) ouvrent tout un nouveau champ d'exploitation.
— Basé sur l'article publié dans Quasar CPC numéro 18, Assembleur : Hardware, par OffseT.
Pour que les choses soient claires, nous allons commencer par revoir le principe de base. Typiquement, une interruption est un signal externe au microprocesseur qui permet d'en modifier le fonctionnement. On entend par là que lorsqu'une interruption est générée par un périphérique et est acceptée par le microprocesseur, celui-ci va exécuter un autre code avant d'acquitter l'interruption (c'est-à-dire indiquer au périphérique que l'interruption a été gérée) et de retourner à son code d'origine. La manœuvre d'acquittement est spécifique au périphérique : sur un CPC standard aucun acquittement d'interruption n'est nécessaire, sur CPC+ celui-ci peut se faire via le registre ASIC DCSR (suivant la configuration du bit 0 du registre ASIC IVR), sur la CPC-ISA ça se négocie directement avec la carte ISA concernée, etc..
La plupart des microprocesseurs acceptent différents types d'interruptions et le Z80 n'a pas failli à cette règle puisqu'il en accepte deux : les interruptions non masquables et les interruptions masquables.
Il s'agit des interruptions qui sont obligatoirement acceptées par le processeur ; celui-ci ne peut pas les ignorer. Lorsqu'une requête d'interruption non masquable est envoyée au Z80, celui-ci termine l'instruction en cours puis effectue un RST &66
. À l'adresse &0066
doit donc se situer la routine2) qui devra gérer l'événement. Une fois le traitement achevé, la routine devra impérativement faire un retour au programme principal via l'instruction RETN
et non RET
ou RETI
! Cette instruction, en plus de recharger dans PC la dernière valeur empilée, va reconfigurer les interruptions non masquable. En effet, une fois que le Z80 a accepté une telle interruption, il refusera toutes les requêtes suivantes jusqu'à ce que l'instruction RETN
soit exécutée ; les interruptions non masquable ne sont pas empilables, une seule à la fois peut être traitée.
Sur CPC, ce type d'interruption n'est pas utilisé par le hardware interne, mais est laissé libre sur le port d'expansion et est géré par exemple, par la Mutiface Two. En effet, lorsque vous activez celle-ci3), elle fait une requête d'interruption non masquable, attend que le Z80 finisse son instruction en cours, et commute sa page ROM en &0000-&1FFF
et sa page RAM en &2000-&3FFF
. De ce fait, lorsque le Z80 fait effectivement son RST &66
en réponse à la requête d'interruption, il saute dans la ROM de la Multiface Two… la suite, vous la connaissez4).
Dans la pratique, ce type d'interruption est parfaitement adapté à des systèmes temps-réel, par exemple pour implémenter un “watchdog”5) ou un système d'alerte hardware.
On retrouve ici les interruptions standard du CPC. Comme le fait remarquer fort judicieusement le petit blond à lunettes, ces interruptions sont dites masquables car elles peuvent être refusées par le Z80 dans le cas où celui-ci est sous DI
(Disable Interrupt). En outre, contrairement aux interruptions non masquables, à partir du moment où le Z80 est sous EI
(Enable Interrupt), il acceptera toutes les interruptions qui se présentent même s'il est en cours d'exécution d'une routine d'interruption ; elles sont empilables. Ceci n'exclue toutefois pas la nécessité d'un acquittement d'interruption pour certains périphériques (ASIC des CPC+, CPC-ISA, etc.).
En outre, vous devez faire attention au fait que lorsque vous entrez dans une routine d'interruption, le processeur passe automatiquement en DI
. Il ne faudra donc pas oublier de le remettre en EI
avant de retourner au programme principal, sinon le Z80 n'acceptera plus aucune interruption ! Enfin, le EI
peut bien entendu être placé plus en amont dans la routine d'interruption6) afin justement de permettre l'empilement des interruptions en provenance de divers périphériques.
En revanche, un RET
suffit en fin de routine d'interruption ; pas d'instruction spécifique comme le RETN
vu précédemment…. Le petit blond à lunettes est en train de s'agiter : “et le RETI
alors, c'est kwa ?”. Comme son nom l'indique, le RETI
est l'instruction de RETour d'Interruption, mais elle est inutile au regard du hardware du CPC, du CPC+, ou même de la CPC-ISA, car elle est dédiée aux périphériques Zilog (Z80CTC, Z80DMA, Z80SIO, etc..). Dans ce cas, son rôle est d'acquitter l'interruption. Mais, je le répète, ceci ne fonctionne que pour les périphériques Zilog, dans tous les autres cas le RETI
se comporte comme un RET
classique (sauf qu'il est plus lent).
Il est par ailleurs important de noter que le Z80 dispose de 3 modes de fonctionnement des interruptions bien distincts, et choisis à l'aide de l'instruction IM
(Interrupt Mode).
Le mode d'interruption 0 (IM 0
), qui n'est là que pour la compatibilité avec le processeur 8080 d'Intel7), est complètement obsolète et ne mérite pas qu'on s'y intéresse. Dans ce mode de fonctionnement, lorsque le Z80 a accepté une interruption, c'est au périphérique de lui donner les instructions à exécuter. Il n'existe pas à ma connaissance de périphérique utilisant cette “fonctionnalité” sur CPC.
Il s'agit là du mode standard utilisé par le firmware. Lorsque le Z80 entre en interruption, il effectue un RST &38
. Sur CPC, le seul périphérique générant des interruptions en interne est le Gate Array. Trois cents fois par seconde, notre petit Z80 saute ainsi en &38
où le firmware a placé son vecteur d'interruption pour gérer le son, les couleurs, le clavier, etc.. Le principal inconvénient de ce mode de fonctionnement vient principalement du fait que tous les périphériques génèrent une interruption à la même adresse (&38
).
Le petit blond à lunette a une fois de plus tenté de faire une remarque constructive ; oui, comme sur CPC seul le Gate Array fait des interruptions, ce mode pourrait suffir. Pourtant, des périphériques externes pourraient parfaitement avoir besoin de générer leurs propres interruptions pour fonctionner… comment allons-nous gérer ce cas de figure ?
La technique utilisée par le firmware pour différencier les interruptions internes des interruptions externes est assez primitive quoique parfaitement fonctionnelle. Il a été décidé que les périphériques externes doivent maintenir la requête d'interruption (signal INT
du Z80) jusqu'à ce que l'acquittement ait été effectué, alors que le Gate Array ne génère qu'une brève impulsion. Ainsi, une fois qu'une interruption a été acceptée, la routine d'interruption système se place sous EI
. Si c'est le Gate Array qui avait généré l'interruption, il n'y a plus de requête d'interruption en cours et rien ne va se passer. Si c'est un périphérique externe qui avait provoqué le RST &38
(par exemple une carte ISA), un nouveau saut en &0038
va aussitôt avoir lieu ! Le petit blond à lunettes commence à s'affoler : “On empile les interruptions, ça va finir par planter !!!” Mais non, pas de panique, car avant de se placer sous EI
dans sa routine d'interruption, le système jongle avec la carry secondaire afin de savoir s'il s'agit d'une nouvelle interruption ou non, et, le cas échéant, saute en &003B
, le vecteur d'interruption réservé aux périphériques externes.
Hum ! Tout ceci est bien compliqué et nous fait perdre pas mal de temps CPU, alors qu'il serait tellement plus simple de différencier nos périphériques matériellement. Dès lors, on comprend qu'il est extrêmement intéressant de pouvoir différencier les interruptions au niveau hardware ; dans le cas contraire, tout le monde se retrouve en &38
et c'est au programme de tester qui a fait l'interruption… pas très commode n'est-ce-pas ?
Voici le mode d'interruption (IM 2
) le plus intéressant ! Il nous permet en effet de choisir l'adresse du vecteur d'interruption pour 128 périphériques différents via une table de vecteurs d'interruptions : le must. Pratiquement, une fois que le Z80 est en IM 2
, lorsqu'une interruption se produit, il forme une adresse à l'aide du registre I (octet de poids fort) et de l'octet placé sur le bus de données par le périphérique ayant demandé l'interruption. L'adresse ainsi obtenue lui donne l'endroit où aller lire l'adresse du vecteur d'interruption à charger dans PC. En clair, en se permettant quelques barbarismes mémoniques, notre Z80 fait :
PUSH PC LD N,"valeur sur le bus de données" LD PC,(IN)
Mais il y a un hic… le hardware de notre bon vieux CPC n'est pas du tout prévu pour fonctionner de la sorte. C'est-à-dire que lorsqu'il génère une interruption, notre Gate Array ne fournit aucun octet au Z80 pour déterminer l'adresse du vecteur d'interruption. Je vous passe les détails électroniques, mais dans une telle situation le Z80 devrait rester en haute impédance et capter une pseudo valeur &FF
pour N. Ceci est effectivement le cas… mais pas toujours !
Pour résumer, disons que si vous utilisez votre CPC normalement (pas de bidouilles hard au niveau CRTC), il saute tout le temps au dernier vecteur de votre tables (N=&FF
). Mais, pour une obscure raison, certains CPC sautent ailleurs de temps en temps. Sur mon bon vieux CRTC type 1, le mode IM 2
est parfaitement stable en &FF
mais sur d'autres CRTC 1 que j'ai pu tester, ça se passe beaucoup plus mal avec des sauts erratiques. En fait, le problème vient vraisemblablement du couple Gate Array / CRTC et du contexte8) au moment de l'interruption… ça n'est pas réellement gérable.
Si vous désirez utiliser le mode vectorisé sur un CPC de base juste pour pouvoir déplacer le vecteur d'interruption, je vous conseille de lire cette section. Pour information, si vous avez un CPC custom, j'ai une bonne nouvelle puisque lorsqu'on branche des cartes additionnelles aux CPC “instables”, ils deviennent - semble-t-il - stables (ça a marché ici pour deux CPC).
N'en déplaise au petit blond à lunettes, le CPC+ est parfaitement étudié pour travailler en mode vectorisé grâce au registre IVR de l'ASIC qui permet de choisir l'adresse de départ de la table des vecteurs d'interruption pour les DMA et le raster. Mais je ne vais pas développer ce sujet ici, vous trouverez tous les détails ici. Sachez tout de même que le mode vectorisé du CPC+, qui est géré normalement, est parfaitement incompatible avec celui que l'on peut improviser sur CPC ancienne génération étant donné que la table des vecteurs d'interruption commence toujours à une adresse paire sur CPC+ et impaire sur CPC old9).
D'aucuns trouveront cette section un peu trop théorique : vous trouverez un exemple d'application concrêt ci-dessous. Enfin, lors de vos essais en IM 2
, je vous conseille vivement de remplir systématiquement toutes les adresses de la table des vecteurs d'interruption non utilisés par un vecteur de “guru”. C'est-à-dire une routine qui vous avertira qu'un saut inattendu a eu lieu et évitera tout plantage ; si tout se passe bien cette routine ne sera jamais exécutée… mais l'expérience m'a prouvé que c'était parfois bien utile ! Si vous utilisez Maxam, je vous conseille une routine du genre :
Guru BRK ; Maxam Breakpoint routine (RST 7) EI ; Notamment celle de Maxam 1.5 RET ; qui est très bien faite
— Extrait de l'article publié dans Quasar CPC numéro 10, Perfectionnement à l'Assembleur, par OffseT.
Sur CPC, dans le cadre classique, le mode vectorisé n'est utilisé que pour déplacer l'adresse des interruptions ; en effet, il n'existe aucun périphérique générant des interruptions vectorisées à gérer sauf hardware additionnel. Nous allons voir ici comment le mettre en place en tenant compte de la compatibilité avec le CPC+, mais sans pour autant faire de configuration du mode IM 2 spécifique à l'ASIC.
Voici une petite routine qui permet de passer le système en mode vectorisé de façon totalement compatible entre CPC ancienne génération et CPC+ (malgré les problèmes de compatibilités soulevés dans la section précédente), et ce sans effectuer aucun test préalable ! Comment est-ce possible ? Simplement en utilisant un vecteur d'interruption à adresse symétrique… le listing étant largement commenté je pense que vous comprendrez de vous même. N'oubliez tout de même pas de faire un petit MEMORY &9FFF
sous BASIC si vous ne voulez pas risquer que le système écrase votre belle table de vecteurs d'interruptions ! En effet, on installe le mode vectorisé sous BASIC à la place du IM 1
par défaut, mais libre à vous d'utiliser ce modèle d'installation du mode vectorisé dans vos productions avec votre vecteur d'interruption perso !
; ; Installation du mode vectorisué pour CPC ; (avec compatibilité CPC plus) ; ; Par OffseT ; pour Quasar CPC numéro 10 ; Org &a000 ; Implantation de la Limit &a0ff ; routine d'installation ; du IM 2 en &A000 ; Routine d'installation Nolist Table Equ &a100 ; Adresse de la table ; des vecteurs ; d'interruption TableH Equ table/256 ; Octet de poids fort di ; Disable Interrupt ld hl,table ; On copie l'adresse du ld bc,inter ; vecteur d'interruption ld (hl),b ; "Inter" à partir de inc l ; "Table" et ce sur une ld (hl),c ; longueur de &100 ld e,l ; octets afin d'être ld d,h ; compatible avec dec l ; tous les CPC... inc e ; (bit 0 IVR = 0 ou 1) ld bc,&ff ; Puis on met l'octet ldir ; de poids fort de ld a,tableh ; l'adresse de la table ld i,a ; des vecteur d'interruption im 2 ; dans I avant de se placer ei ; en IM 2 et de revenir ret ; innocemment... List Org &a2a2 ; On implante le vecteur Limit &a3ff ; d'interruption à une ; adresse symétrique pour ; être compatible entre ; CPC "plus" et "old" ; Vecteur d'interruption Nolist Inter jp &b941 ; Ici on lance en exemple ; la même routine ; d'interruption que celle ; du système
&ABAB
ou &9898
par exemple.