ASMtrad CPC

Apprenez l'assembleur Z80

Interruptions - modes et fonctionnement.

Les interruptions qu'est-ce que c'est ?

Concrètement il s'agit d'un signal externe au microprocesseur qui permet d'en modifier le comportement.

Quand une interruption est générée par un périphérique et qu'elle est acceptée par notre Z80, celui-ci va interrompre ce qui était en court et sauter à une adresse spécifique
A cette adresse se trouvera une routine qui traitera l'interruption.
Ceci étant fait, le Z80 acquittera l'interruption (ou pas...) en signalant au périphérique que celle-ci a eut lieue et retournera là ou il était avant celle-ci.

Si vous n'utilisez pas d'interruptions raster ou DMA, vous n'aurez pas à faire d'acquittement spécifique (puisque seul le Gate Array demandera des interruptions).

En revanche, si vous utilisez les interruptions rasters ET les interruptions DMA (si vous n'utilisez qu'un type d'interruption cela ne compte pas, je parle bien d'en utiliser plusieurs), alors vous pourrez avoir recourt à l'acquittement spécifique des interruptions (mais la encore ce n'est pas obligé, tout dépend ce que vous en faites...).

Bref, nous allons voir tout cela en détail ci après...

Afin de faire les choses correctement nous devons discerner deux types d'interruptions:
-Les interruptions non masquables (dont on se moque profondément sur gx4000, puisqu'il ne peut y en avoir).
-Les interruptions masquables.

Les interruptions non masquables

Elles ne sont JAMAIS utilisées par le hardware interne.
Un CPC+ à nue n'aura donc jamais d'interruptions non masquables.
Pour une GX4000 n'en parlons même pas, puisqu'il n'y a pas de port extension et donc aucune possibilité de brancher quelque extension que ce soit utilisant ce type d'interruption.

Ces interruptions sont obligatoirement acceptées par le Z80.
C'est à dire qu'il peut être déjà en train de traiter une interruption voir même être sous DI (interruptions interdites), peut importe, l'interruption aura lieue !!!

Ainsi, lorsqu'une interruption non masquable est envoyée, le Z80 termine l’exécution de l'instruction en court puis fait un RST #66 (voir le chapitre traitant des RST).
En #66 vous aurez donc pris soin de placer la routine gérant l'évènement bien entendu.

A la fin de votre routine vous devrez placer un RETN.
Pourquoi un RETN plutôt qu'un RET ou un RETI ? Parce que RETN reconfigure les interruptions non masquables.

Tant que le Z80 ne rencontrera pas de RETN, l'interruption ne sera pas terminée, celui-ci refusera toute autre demande d'interruption.
C'est aussi pour cela que les interruptions non masquables ne peuvent en aucun cas se superposer (ou s'empiler si vous préférez).
Il ne peut donc y en avoir qu'une seule à la fois

Les interruptions masquables

Se sont toutes les autres interruptions.
On les dit masquables justement parce qu'elles peuvent être ignorées (et donc masquées).
Effectivement, si vous êtes sous DI alors aucune de ces interruptions n'aura lieue...
Se sont les interruptions "normales" du cpc, celles que vous avez en temps normal quand aucun périphérique n'est branché sur le port extension.

Les interruptions masquables sont empilables: pendant qu'une int à lieue et que la routine gérée pour celle-ci est en court, une autre int peut alors arriver et interrompre celle en court...
Ce qui peut vite devenir cause de soucis si vous n'aviez pas prévu la chose.
Cependant en temps normal (encore), cela ne risque pas spécialement d'arriver, puis-qu’après le saut vers la routine d'int, le Z80 est automatiquement sous DI.

Enfin un simple RET terminera votre routine d'int.

Notre Z80 dispose en outre de différents modes d'interruptions.
3 modes mais nous ne parleront ici que de 2 étant donné que le mode 0 ne sert à rien...

Le mode IM 1:

Le mode IM 1 est pouvons nous dire le mode simple.
Ici pas de gestion particulière des interruptions, toutes sauteront au même endroit et aucune différence ne sera faite de base (ce qui ne veut pas dire que vous ne pourrez pas les différencier vous même).

Lorsqu'une int intervient dans ce mode, le Z80 effectue un RST #38 et saute donc en #38 ou se trouvera votre routine.

Je ne détaillerai pas plus ce mode d'int ici car ceci est déjà fait dans le cour dédié aux interruptions sur cpc old.

Le mode IM 2:

Voici un mode qui va franchement nous intéresser. Si en IM 1, toute int saute en #38, en IM 2 ca ne sera pas le cas.

Sur CPC "old", ce mode était quelque peu "bugué". Sur CPC+/GX4000 ce n'est pas le cas.

L’intérêt de ce mode est de pouvoir différencier vos int, ce qui est plus que nécessaire puisque nous avons des int raster et 3 int DMA possibles...
C'est donc très utile de pouvoir les différencier et de savoir qui a provoqué l'int.

Ainsi, chaque int sautera à une adresse spécifique en fonction du périphérique qui l'aura provoquée.

Voyons donc comment l'adresse de saut est déterminée en IM 2:

Lorsqu'une int intervient en IM 2, le poids fort d'une adresse est pris avec le registre I.
Le poids faible de l'adresse, lui, est fonction du registre IVR de l'ASIC qui est situé en #6805.

Voici le fonctionnement d'IVR: (N'oubliez pas de connecter la page I/O ASIC pour l'utiliser !!!)

ivr

Pour les bits 7 à 3, vous choisissez ce que vous voulez. Pour l'exemple gardons les à 0 et disons que I contient #40.

Aussi, seuls les bits 2 et 1 détermineront notre adresse, mais vous ne décidez pas de ces bits comme vous l'avez compris.

Il faut noter que le bit 0 n'est pas pris en compte pour l'adresse. Aussi, vous devrez placer vos adresses de saut à des adresses paires !!!

Si pour l'exemple, les bits 7 à 3 sont à 0 et note I=#40 alors les sauts seront les suivants:

DMA son 2: #4000
DMA son 1: #4002
DMA son 0: #4004
DMA raster: #4006

Á ces adresses, vous placerez alors l'adresse de saut pour chaque DMA.

Quand une interruption aura lieu, l'adresse de saut sera automatiquement lue et le saut à l'adresse fonction du DMA ayant provoqué l'int sera fait.

Les acquittements:

Comme nous pouvons le voir, le choix nous est donné d'avoir des acquittements spécifiques ou universels.

Mais qu'est-ce donc ?

L’intérêt est simple. Imaginons que pendant que vous êtes sous DI, une int se présente.

- En acquittement universel, il ne se passera rien de spécial: l'int sera ignorée tout simplement. Aussi cette int n'aura donc jamais lieu.
- En acquittement spécifique, l'int sera elle maintenue jusqu'à ce vous signalez que celle-ci a bien été traitée. Ainsi, même si vous étiez sous DI, dès qu'un EI se présentera, votre int aura lieue (et si plusieurs attendent, bon courage pour tout gérer correctement car elles vont s'empiler...).

En acquittement spécifique, vous devrez donc signaler que l'int a été traitée.
Pour se faire nous disposons du registre DCSR situé en #6C0F:

DSCR

Lorsqu'une int est en attente, celle-ci passe en DI. Il vous faudra donc la passer en EI et poker un 1 dans le bit voulu.

Le bug des interruptions cpc+

Tout ca c'était vraiment magnifique (et ça l'est), mais c'était sans compter sur un bug vicieux.
En effet, normalement, tout devrait se passer pour le mieux et chaque DMA devrait sauter à une adresse spécifique comme tout le monde en rêve... Sauf que non !!!

Amstrad c'était rendu compte d'un bug sur les interruptions des CPC+ et avait même été jusque retirer des documentations les plus récentes les chapitres sur les interruptions vectorisées ainsi que sur IVR (Vecteur d'interruption dont je parle au dessus).

Le bug est le suivant: lors d'une interruption, le bit 1 d'IVR saute et n'est pas fiable passant de 0 à 1 de façon étrange.

Si vous n'utilisez que les interruptions raster, vous ne vous en rendrez pas compte, c'est surtout si vous passez en IM 2 que le problème se posera puisque vous ne sauterez pas au bon endroit en fonction de l'interruption (Raster ou un des DMA).

Le bug est fonction de l'instruction en court au moment ou une interruption arrive.
En gros, votre code est exécuté, et, alors qu'une interruption est provoquée, en fonction de l'instruction provoquée il y aura bug ou pas...
Ceci n'arrive que sur des instructions ayant des actions sur la RAM (lecture/écriture) mais sur aucune instruction à un seul octet d'opcode...

Ceci pourrait donc être gênant si la source n'avait été identifiée.

Le problème vient en fait de l'endroit ou se trouve le code interrompu par l'interruption.

En effet, selon l'endroit ou votre code est en RAM, le bug ne se produit plus...
Le bug n'a lieu que si le bit 13 de l'adresse du code en court est à 0.
Afin d'éviter ce bug il suffit donc que votre code potentiellement interrompu par une interruption soit placé à des adresses ou le bit 13 de celle-ci est à 1. Vous n'aurez alors plus de problème.

Voici donc un récapitulatif de ces adresses:

bug dma

Bien entendu, pour tout code sous DI vous n'avez pas à vous embêter avec ce soucis...

Autre moyen de palier au bug: passer à l'acquittement spécifique. Vous pourrez alors lire les bits 4 à 7 de DCSR pour savoir qui a provoqué l'interruption.

Pour terminer sur les int, je vous propose un petit exemple de mise en place du mode IM 2.

                        ORG     #A000
                        NOLIST
                        CALL    DELOCK        ;sous routine de delockage de l'asic
                        DI                    ;on coupe les int pour éviter qu'il y en ait pendant qu'on modifie leur fonctionnement
                        LD      BC,#7FB8
                        OUT     (C),C         ;on connecte la page I/O ASIC
                        LD      A,#20         ;note table de vecteurs de saut sera en #2000
                        LD      I,A           ;on place dans I notre poids fort de la table de vecteurs
                        LD      A,0       
                        LD      (#6805),A     ;on met 0 dans IVR. Notre poids faible sera donc de 0
 
            ;A partir d'ici tout est initialisé. Nous reste à placer en #2000 nos adresses pour les sauts.

                        LD      HL,DMAson2
                        LD      (#2000),HL    ;en #2000 nous aurons l'adresse de saut vers la routine DMAson2
                        LD      HL,DMAson1
                        LD      (#2002),hl    ;en #2002 nous aurons l'adresse de saut vers la routine DMAson1

            ;vous devez ajouter les autres int DMA si vous les utilisez. Pour l'exemple je m'arrete la
            ;vous devrez aussi créer les sous routines DMAson2; DMAson1 et autres

                        IM      2             ;on passe en IM2
                        EI                    ;on rétablit les interruptions
                        RET                   ;fin de notre prog

            DELOCK      LD      HL,TASIC
                        LD      D,17 
            DELOCKK     LD      BC,#BC00         
                        LD      A,(HL)         
                        OUT     (C),A         
                        INC     HL         
                        DEC     D         
                        JP      NZ,DELOCKK
                        RET

            TASIC       DB      255,0,255,119,179,81,168,212,98,57,156,70,43,21,138,205,238