Table des matières

Retourner au sommaire

Les interruptions

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.

Généralités

Basé sur l'article publié dans Quasar CPC numéro 18, Assembleur : Hardware, par OffseT.

C'est quoi une interruption

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.

Les interruptions non masquables

Celle-ci tu ne pourras pas l'éviter mon gars ! 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).

HALT, pas si vite ! 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.

Les interruptions masquables

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

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.

Hop ! Hop ! Hop !

Le mode d'interruption 1

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 ?

Une bonne méditation est souvent nécessaire pour tout comprendre aux interruptions

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 ?

Le mode d'interruption vectorisé

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 :

l'IM 2, une méthode musclée de gestion des interruptions !

PUSH PC
LD N,"valeur sur le bus de données"
LD PC,(IN)

La haute impédance

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.

La solution

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

Le CPC+ dans tout ça ?

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

Programmons un peu

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

Sans maîtrise la puissance n'est rien

Le mode vectorisé sur CPC

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.

Contourner le problème de compatibilité

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 !

Listing : Mode vectorisé passe-partout

;
; 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
1) la PRI, Programmable Raster Interrupt
2) le vecteur d'interruption non masquable
3) en appuyant sur son bouton STOP
4) Et si ça n'est pas le cas, vous avez juste à aller lire le magnifique article sur la Multiface Two.
5) chien de garde pour les anglophobes
6) généralement juste après l'acquittement et les éventuels traitements critiques
7) le microprocesseur dont le Z80 s'inspire
8) l'état hardware du Z80, du CRTC, du Gate Array, etc.
9) La solution pour retrouver une compatibilité consiste en fait à utiliser un vecteur d'interruption à une adresse symétrique du type &ABAB ou &9898 par exemple.