Programmation Assembleur

Temps d'accès à la VRAM

Cet article a été mis à jour, vous consultez ici une archive de cet article!

Problématique



Le processeur graphique du MSX (appelé VDP) fonctionne de façon autonome du processeur central (CPU), le Z80.

Le CPU communique avec le VDP à travers des ports d'entrée/sortie (ports I/O).
Ces ports I/O permettent de lire et/ou d'écrire dans les registres qui contrôlent le fonctionnement du VDP (choix du mode graphique, taille des sprites, etc.), mais aussi de lire ou écrire dans la mémoire vidéo (VRAM) du VDP.

La lecture/écriture en VRAM se fait de façon indirect : on configure les registres du VDP (pour indiquer notamment l'adresse à accéder en VRAM), puis on lit/écrit la VRAM à travers les ports I/O.
Le VDP doit traiter ces demandes d'accès de son coté.
Hors, quand il est occupé à afficher une image à l'écran, le VDP peut prendre plus de temps à répondre à ces demandes que de temps qu'il faut au CPU pour les faire.
Il peut donc arriver que le CPU envois des données trop rapidement au VDP et que certaines soient perdues, faute de temps pour les traiter.
Il en résulte un affichage incorrect des données que le programme souhaitait envoyer.
Idem si le CPU essaye de lire trop vite des données en VRAM : Les valeurs récupérées peuvent être incorrectes.

Le temps de traitements des demandes d'accès à la VRAM dépend de certains paramètres :
- La génération du VDP,
- Le mode écran (screen mode) actuel,
- L'activité du VDP au moment de la demande.

Génération et mode écran



Durant la période commerciale du MSX, 3 générations de VDP ont été utilisés :
- Texas Instrument TMS9918 (MSX1),
- Yamaha V9938 (MSX2),
- Yamaha V9958 (MSX2+ et turbo R).

Voici l'intervalle de temps minimale à respecter entre des accès au VDP en fonction des modes écrans (exprimé en nombre de cycles du CPU).
Screen mode VDP mode MSX1 cycles MSX2/2+ cycles
0,W40 T1 12 20
0,W80 T2 -- 20
1 G1 29 15
2 G2 29 15
3 MC 13 15
4 G3 -- 15
5 G4 -- 15
6 G5 -- 15
7 G3 -- 15
8 G7 -- 15

Bien que plus moderne, le V9938 est moins réactifs que le TMS9918 dans le modes textuel 1 (Screen 0) et multi-couleur (Screen 3).

Activité du VDP



L'activité du VDP au moment de la demande d'accès à un gros impact sur son temps de réaction.
Par ex., lorsque le VDP est en train de "dessiner" les bordures, son temps de réaction est plus rapide que le temps minimum nécessaire pour le CPU pour faire une demande d'accès à la VRAM.
Ainsi, dans certains cas, il n'y a aucun contrainte de temps d'accès à la VRAM :
- Quand le VDP est dans le H-Blank (bordure droite/gauche), ou le V-Blank (bordure bas/haut). Note : Le V-Blank étant relativement long, il est possible d'utiliser ce temps pour lire/écrire des données en VRAM a une vitesse maximale.
- Quand l'affichage est désactivé. Note : Par ex., on peut utiliser un écran noir (affichage désactivé) pour charger plus rapidement un niveau de jeu.

L'activation ou non des sprites a un impact sur la réactivité du VDP, mais l'impact chiffré exacte sur les temps d'accès n'est pas connu.

Accès au VDP en assembleur



Le BIOS permet d'accéder au VDP de façon sécurisé et standard. Cependant, le surcout en performant de passer par le BIOS est loin d'être négligeable et dès l'époque commercial du MSX, les développeurs ont utilisé les accès direct en assembleur pour pouvoir tirer pleinement parti de la puissance du VDP.

Le CPU communiquant avec le VDP via les ports I/O, ce sont les instructions in et out (et leurs dérivés) qui sont utilisés.

Dans la suite de ce chapitre, nous n'allons aborder que les accès en écriture de la VRAM (cas le plus fréquent), mais toutes les contraintes décrites ci-dessous s'applique exactement de la même façon pour la lecture en VRAM.

Remplissage



Après la configuration des registres pour accéder à la VRAM de façon séquentielle (qui n'est pas expliqué ici), le moyen le plus rapide d'écrire 4 octets en VRAM est :

; Ecrire la valeur de A dans 4 octets consécutif en VRAM
out (0x98), a
out (0x98), a
out (0x98), a
out (0x98), a


L'instruction out (n),a prenant 12 cycles sur MSX, elle est trop rapide pour la plus part des modes d'affichage (en dehors du V-Blank).

L'instruction out (c),a (ou le numéro de port I/O se trouve dans le registre C) prend elle 14 cycles sur MSX. Ce qui est un chouilla trop rapide pour la plupart des modes d'affichage. Ajouter un nop entre chaque out (c),a permet d'arriver à 15 cycles, l'intervalle minimale d'accès à la VRAM des modes bitmap du MSX2 (comme le Screen 5 par ex.).

Par contre, cette solution est encore trop rapide pour les modes graphiques sur MSX1 (29 cycles) ou les modes textes sur MSX2 (20 cycles). Dans ces cas, le mieux est d'intercaler d'autres instructions entre les out pour les écarter suffisamment dans le temps, sans perdre du temps CPU à ne rien faire avec une longue série de nop .

Code les plus rapides pour remplir la VRAM d'une valeur en fonction des mode d'affichages :
Screen 0 MSX1 Screen 3 MSX1 Screen 1~8 MSX2 Screen 0 MSX2 Screen 1~2 MSX1
; 12 cycles
out (0x98),a
out (0x98),a
out (0x98),a
out (0x98),a
        
; 13 cycles
out (0x98),a
nop
out (0x98),a
nop
out (0x98),a
nop
out (0x98),a
nop
        
; 15 cycles
ld c, 0x98
out (c),a
nop
out (c),a
nop
out (c),a
nop
out (c),a
nop
        
; 20 cycles
        
; 29 cycles
ld c, 0x98
ld b, #4
loop:
out (c),a
nop
djnz loop
        


Copie



Les exemples précédent ne permettent que de remplir une zone de VRAM avec une valeur unique. C'est utile pour effacer une zone de VRAM par ex., mais la plus part du temps, le programme va vouloir envoyer des données graphiques qui viennent de la mémoire du CPU (un image, un sprite, etc.) et il faut donc utiliser les instructions Z80 qui le permettent.

outi et outd permettent d'envoyer vers le VDP la valeur contenu à l'adresse pointé par le registre HL puis incrémente/décréemente HL.

Une fois avoir configuré HL pour pointer vers ses données en mémoire et les registres du VDP pour pointer vers la destination dans la VRAM, le moyen le plus rapide de transférer des données est :

; Ecrire la valeur de (HL++) dans 4 octets consécutif en VRAM
outi
outi
outi
outi


L'instruction outi prend 18 cycles, ce qui est suffisant pour tous les modes sauf les modes graphiques sur MSX1 (29 cycles) ou les modes textes sur MSX2 (20 cycles).
outir prend 23 cycles mais permet de choisir un nombre variable d'accès à la VRAM avec une seule instruction (le nombre d'itération se trouvant dans le registre B).

Pour les modes textes sur MSX2, il suffit d'ajouter 2 nop entre chaque outi pour respecter l'intervalle de temps minimale d'accès.

outir ne peut être "ralenti" avec des nop vu que la boucle est géré en interne par le Z80.
Pour avoir une boucle qui respecte l'intervalle de 29 cycles nécessaires pour les modes graphiques sur MSX1, vous pouvez utiliser :

; Boucle de 29 cycles (18 + 11)
OutiToVram:
    outi              ; send (HL) ; HL++ ; B--
    jp nz, OutiToVram ; while register B not 0


Performance



Temps d'exécution des instructions de I/O



Instruction Cycles (T-States) Timing I/O
in a,(n) 12 9|3
out (n),a 12 9|3
in a,(c) 14 11|3
out (c),a 14 11|3
outi 18 15|3
outd 18 15|3
ini 18 12|6
ind 18 12|6
otir 23 (loop)
18 (last)
15|8
15|3
otdr 23 (loop)
18 (last)
15|8
15|3
inir 23 (loop)
18 (last)
12|11
12|6
indr 23 (loop)
18 (last)
12|11
12|6