ASMtrad CPC

Apprenez l'assembleur Z80

Notion et evaluation du temps machine:

Je vais être obligé de faire un petit aparté...

Comme il va me falloir vous donner une notion de temps machine, je vais être obligé de vous expliquer comment faire un raster.
Un raster tout bête, à savoir juste changer une couleur à un moment précis.

Sauf que comme nous allons le voir, le system (BASIC; Firmware qui travaille tant qu'on ne l'en empèche pas) va vite venir nous embêter.
Aussi nous verrons comment supprimer celui-ci afin qu'il nous laisse tranquille !!!

Qu'est-ce qu'un raster:

Comme vous le savez, notre CPC est capable d'afficher des graphismes avec au choix 2;4 ou 16 couleurs sans contrainte suivant le mode.
C'est bien mais on peut faire mieux...

Imaginez vous en mode 2, avec vos deux pauvres couleurs, alors que le cpc old possède une palette de 26 couleurs différentes (4096 pour un CPC+).

Le CPC, comme beaucoup de machines de l'époque peut avoir à l’écran plus de couleurs que la limite théorique, et ce justement grâce aux fameux rasters.

Admettons que nous sommes en mode 2. Nous avons donc 2 encres: l'encre 0 et l'encre 1.
Imaginez maintenant ce que fait le moniteur (l'objet sur lequel votre image s'affiche. Notez que je ne parle pas d'écran qui lui est une notion d'image.)...

Pour afficher une image sur sa surface, le moniteur déplace un faisceau d’électrons qui vont venir exciter la surface du moniteur.
C'est ainsi que le moniteur fait apparaitre des pixels et que nous voyons alors se former une image.

Mais le canon à électron n’envoie pas l'image d'un coup. Non. Il envois petit à petit les pixels à la surface du moniteur.

Commençons à voir comment le moniteur affiche en partant du haut.
Et cela tombe bien c'est la méthode choisie par celui-ci pour afficher (sur un vectrex ou sur un oscilloscope, le flux est envoyé suivant un trajet de droite. Aussi, le canon n'a pas de trajet type et se déplace en fonction de ce qu'il y a à afficher. Ce n'est pas le cas sur cpc ou sur la plupart des machines heureusement).

Pour se caler sur le haut du moniteur, celui-ci a donc besoin de ce qu'on appelle un signal de synchronisation.
Ce signal de synchro permet au moniteur de savoir ou est le début mais aussi la fin d'une image à afficher.

Nous avons 2 signaux:

- La VBL (Vertical Blanking): qui permet au moniteur de se caler verticalement.

- La HBL (Horizontal Blanking): qui permet au moniteur de se caler horizontalement.

Ces deux signaux sont générés par le CRTC, via plusieurs registres, dont le 7 et le 2 qui permettent respectivement de les déplacer verticalement et horizontalement.

Essayez sous BASIC un:

OUT &BC00,7:OUT &BD00,31

Vous verrez l'image se décaler verticalement, ce qui est logique puisque vous venez de déplacer le signal vbl d'un bloc (8 lignes pixels).

Une fois calé sur le signal VBL et sur la HBL, le canon à électron commence à envoyer l'image au moniteur.

Son trajet est le suivant:
Partir de la gauche et aller vers la droite, jusqu'à la prochaine HBL.
Revenir à gauche (sans rien afficher),une ligne pixel en dessous et recommencer jusqu'en bas où il rencontrera le signal VBL.
A ce moment là ,il reviendra en haut à gauche sans rien afficher pendant le retour (heureusement).

parcours du canon à électrons

Vous vous demandez certainement ou je veux en venir avec mon histoire de moniteur et de canon à électrons ?

Imaginez: pendant que le canon à électrons descend, vous changez la couleur d'une encre...

A la première ligne, vous dites: encre 1 en bleu foncé.
Vous attendez un peu que le canon à électron termine sa ligne bleu et revienne à la ligne suivante à gauche et hop, vous dites: encre 1 en bleu vif...
Et de même ligne suivante: encre 1 bleu pale etc etc...
Vous serez toujours en mode 2, mais vous aurez changé la couleur de l'encre à chaque ligne !!!

Un raster c'est ca: un changement de couleur durant le balayage de l'écran par le canon à électron !!!

Bon tout ca c'est gentil mais ca ne nous dit pas comment faire un raster...

Faire un raster:

Avant de faire un raster il va falloir se caler nous même sur l'écran.

Pour cela il y a un moyen simple: tester le signal VBL !!!
Puisque le moniteur se cale dessus pour commencer son affichage, en testant le signal VBL nous serons donc en haut !!!

Pour tester le signal VBL, nous avons la chance d'avoir un bit sur le port B du PPI qui nous indique si le signal VBL est en court ou pas (ce signal durant pas mal de temps, il est alors possible de le capter).

Le port B du PPI s'adresse via #F5. En voici un schéma allégé pour ne voir que le bit qui nous intéresse.

ppi port B

Aussi pour tester ce port, nous allons faire une boucle de lecture.

Pour lire, rien de plus simple il suffit d'utiliser l'instruction IN A,(C)

A contiendra la valeur lue sur le port donné dans B.

Voici donc notre test de VBL que vous connaissez de toute façon déjà:

                FRAME           LD      B,#F5
                FRM             IN      A,(C)
                                RRA
                                JR      NC,FRM

Là je vous vois venir: "C'est quoi RRA ??? Et pourquoi on utilise ça ???"

Comme vous pouvez le voir sur le schéma du port B du PPI, notre signal VBL est donc sur le bit 0 du port B.

Le RRA est une instruction de rotation qui fait "scroller" les bits du registre A vers la droite. Le bit sortant est placé dans la carry.

rra

Ainsi en faisant un RRA, le bit 0 atterrit directement dans la Carry qui sera donc mise à 1 si celui-ci l'était lui aussi.
Comme le bit 0 est à 1 quand le signal VBL est en court, tester ce bit revient donc à tester le signal VBL.

Le test avec JR NC,adr signifie: Saut relatif (c'est un saut avant ou après la position de l'instruction) si "Non Carry" (donc si la carry=0).
Ainsi, tant que le bit du signal VBL n'est pas mis à 1, on continue de tester.

Maintenant que nous sommes en haut de l'écran, nous sommes tout de même bien embêté...
Le haut de l'écran c'est sous le plastique. Il nous faut donc descendre un peu.

On pourrait faire une boucle, avec une certaine valeur pour attendre, mais ce serait perte de temps.

Heureusement pour nous le cpc possède un système d'interruptions et nous pouvons les détecter.

Une interruption, c'est tout simplement un signal qui stoppe ce qui est en court pour aller faire autre chose.

Nous avons plusieurs modes d'interruptions possible, mais seuls 2 nous sont utiles: le mode IM1 et le mode IM2.
Par défaut le cpc boot en IM1.

L'IM 1 (ou Interruption Mode 1), c'est simple: quand cette int est provoquée, le Z80 sauvegarde l'adresse courante dans la pile et saute en #38.
On verra un peu plus loin ce qu'il s'y passe...

Voyons plutôt comment détecter une interruption.

Pour cela nous avons une instruction très pratique: HALT !!!
Un HALT c'est simple: ca attend la prochaine interruption.

Faisons donc un programme tout bête:
- On attend la VBL.
- On change la couleur du border.
- On attend la prochaine int avec un HALT.
- On change la couleur du border etc etc... On le fera 6 fois...

Me reste donc à vous expliquer comment changer la couleur d'une encre avant de faire notre routine.

Sélection de l'encre:

Pour sélectionner l'encre, comme pour en changer la couleur, nous allons passer par le Gate Array (GA).

Le port du GA est le #7F.

Voici un petit schéma pour la sélection de l'encre:

ga selection encre

Ainsi pour sélectionner le border nous pourrons faire un:

                                LD      BC,#7F00+%00010000
                                OUT     (C),C

Reste plus qu'à donner une couleur:

Pour cela, il suffit d'envoyer au GA un octet dont les bits 7;6 et 5 sont à %010 suivit de la couleur.

Le tableau suivant vous donnera d'une part la correspondance couleur BASIC vers couleur GATE ARRAY, mais vous donne aussi les valeurs GA directement avec les bits 7;6 et 5 mis à la bonne valeur (colonne GA+64) ou sans (colonne GA):

couleurs cpc

Prenez donc les couleurs GA.
Elles sont en décimal. Les valeur OLD sont les valeurs sous basic. Les valeurs "PLUS" sont les valeurs RVB sur CPC+.

Ainsi pour mettre le border en noir, vous pourrez faire:

                                LD      BC,#7F10:OUT (C),C                  ;#10 met bien le bit 4 à 1 et selectionne donc le border
                                LD      A,84:OUT (C),A                      ;B contenant déjà #7F inutile de le redonner.

Revenons à nos moutons et passons à nos changements de couleurs à chaque HALT:

                                ORG     #8000
                                RUN     $
                                NOLIST

                FRAME           LD      B,#F5
                FRM             IN      A,(C)
                                RRA
                                JR      NC,FRM

                                LD      BC,#7F10:OUT (C),C          ;border sélection
                                LD      A,84:OUT (C),A              ;noir

                                HALT                                ;on attend une int
                                LD      A,68:OUT (C),A              ;bleu foncé
                                ;notez que je n'ai pas sélectionné le border puisque c'était déjà fait

                                HALT                                ;on attend une int
                                LD      A,85:OUT (C),A              ;bleu vif

                                HALT                                ;on attend une int
                                LD      A,87:OUT (C),A              ;bleu pâle

                                HALT                                ;on attend une int
                                LD      A,83:OUT (C),A              ;bleu ciel

                                HALT                                ;on attend une int
                                LD      A,75:OUT (C),A              ;blanc

                                HALT                                ;on attend une int
                                LD      A,76:OUT (C),A              ;rouge

                                JP      FRAME

On exécute et la... Horreur !!! Ca clignote !!!

Pourquoi:

Le system, qui gère votre joyeux basic, tourne toujours en partie via les interruptions.
En effet, le basic se sert des interruptions pour entre autre rafraichir les couleurs; tester le clavier et faire plein d'autres choses !!!
En l’occurrence, le system remet les couleurs choisies en basic de temps en temps ce qui provoque le clignotement que vous pouvez voir. Mais pas que.

Vous ne le voyez peut être pas mais en plus ça bouge...
Le début de vos rasters n'est pas fixe, ce qui la encore s'explique par les int system qui ne font pas les choses de façon fixes (ce qui est bien normal).

Alors bon, comment faire ?

La solution est simple: supprimer les sauts system.

Comme je vous l'ai dit, lorsqu'une int est provoqué en IM1, le Z80 saute en #38.
Et à cette adresse, on trouve donc des sauts vers les routines du system.

Il suffit donc de détruire ces sauts en ajoutant en #38 un EI:RET

Le EI rétablit les interruptions (car au saut vers une int, le Z80 passe automatiquement sous DI: il n'y aura plus d'interruptions).
Le RET permettant de retourner ou nous étions.

Ajoutons donc avant notre FRAME:

                                DI                                  ;on coupe les int au cas ou.
                                LD      HL,#C9FB                    ;#C9=RET ; #FB=EI
                                LD      (#38),HL                    ;on envois ces octets en #38. N'oubliez pas l'inversion des octets lors de l'envoie d'un reg 16 bits
                                EI                                  ;on rétablit les int

On execute et oh magie: ca ne clignote plus !!! A nous les couleurs partout sur l'écran !!!

Ce que vous venez de faire se nomme des RASTER. Vous avez changé la couleur d'une encre plusieurs fois pendant une frame.

Évaluer visuellement le temps machine pris par une routine:

Si je vous ai expliqué maintenant comment faire un raster, c'est tout simplement parce que cela va nous être utile pour mesurer rapidement le temps pris par nos routines.

Le principe est simple: avant la routine on donne une couleur; après on la change.
Ainsi, la première couleur durera le temps pris par notre routine !!!

Prenons un exemple simple avec une routine toute bête d'affichage de sprite:

                                ORG     #8000
                                RUN     $
                                NOLIST

                                DI
                                LD      HL,#C9FB
                                LD      (#38),HL
                                EI

            FRAME               LD      B,#F5
            FRM                 IN      A,(C):RRA:JR NC,FRM

                                HALT:HALT                           ;on saute 2 halt histoire d'etre dans l'ecran visible
                                LD      BC,#7F10:OUT (C),C          ;sélection du border
                                LD      A,76:OUT (C),A              ;sélection couleur rouge

                                LD      HL,#8000
                                LD      DE,#C000
                                LD      B,8
            SPRITE              PUSH    BC
                                PUSH    DE
                                LD      BC,4
                                LDIR
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE
                                POP     BC
                                DJNZ    SPRITE

                                LD      BC,#7F10:OUT (C),C          ;sélection du border
                                LD      A,84:OUT (C),A              ;sélection couleur noir

                                JP      FRAME

A l’exécution on obtient ceci:

cpu1

Notre raster rouge correspond donc au temps pris par notre routine.

Remplaçons le LDIR par des LDI et déroulons la boucle...

                                ORG     #8000
                                RUN     $
                                NOLIST

                                DI
                                LD      HL,#C9FB
                                LD      (#38),HL
                                EI

            FRAME               LD      B,#F5
            FRM                 IN      A,(C):RRA:JR NC,FRM

                                HALT:HALT                           ;on saute 2 halt histoire d'etre dans l'ecran visible
                                LD      BC,#7F10:OUT (C),C          ;sélection du border
                                LD      A,76:OUT (C),A              ;sélection couleur rouge
                                LD      HL,#8000
                                LD      DE,#C000
            SPRITE              PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE

                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE
                                
                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 

                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 

                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 

                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 

    
                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 

                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 

                                PUSH    DE
                                LDI:LDI:LDI:LDI
                                POP     DE
                                EX      HL,DE
                                LD      BC,#0800
                                ADD     HL,BC
                                EX      HL,DE 
    
                                LD      BC,#7F10:OUT (C),C          ;sélection du border
                                LD      A,84:OUT (C),A              ;sélection couleur noir

                                JP      FRAME

Résultat:

cpu2

Si vous comparez les deux on a bien gagné du temps !!!

cpu1cpu2

Ainsi, en utilisant de simples rasters, vous pouvez estimer rapidement votre temps machine
Ceci vous permet donc de voir ce qui vous prend du temps par exemple en changeant de couleur pour chaque routine