Saturday, January 15, 2022

Le HDMA

Sur SuperNES, un grand nombre d'effets intéressants dépend de la programmation du HDMA: une sorte de 'blitter' synchronisé sur les retours horizontaux (Pour ceux d'entre-vous qui n'ont pas regardé une vidéo sur l'Amiga la semaine dernière, traduisez "un co-processeur déclenché à chaque retour de ligne"). A-t-on quelque-chose d'équivalent sur Nintendo DS ?

The SNES had an interesting circuit that could (among other things) copy an array into one (or a set of) register(s) while synchronizing with screen display: the HDMA. And an impressive number of effects in SNES games were actually involving that circuit. What about the DS? Does it do HDMA as well ?

For those of you who have never programmed an 8-bit or a 16-bit system at metal level, DMA stands for "Direct Memory Access", and it is usually a chip or a circuit of a chip that will peek and poke data on the memory bus without involving the main processor. It can boost memory copies, or deliver samples to the sound chip, for instance. Nintendo Consoles have multiple channels available for DMA transfers, each channel with its own source, destination and count registers to identify what is to be copied and where it goes.

Un DMA, c'est un circuit qui va se faire passer pour le CPU sur un bus et ordonner des lectures ou des écritures de données. C'est surtout utile quand on peut effectuer des transfers entre de la mémoire et des des registres hardware. Le processeur peut alors s'occuper d'autre chose. La première fois que j'ai joué avec ça, c'était pour mon modplayer SoundBlaster.

Les consoles Nintendo ont plusieurs 'canaux' DMA, chacun responsables d'un transfert entre une source et une destination. Il y a un mécanisme de priorité fixe entre ces canaux: si deux canaux essaient d'utiliser le bus mémoire simultanément, celui ayant le numéro le plus élevé est mis en pause le temps que celui ayant le numéro le plus faible ait fini. Le processeur, lui, passera après tous ces transfers DMA.

So I could prepare a list of colours and transfer them by DMA into a palette entry, but that would be of little use: I would have no control over the speed at which new values are written in the palette and thus I would have no idea of the effect it would have on screen. If I check the DMAxCNT GBA registers, I can find a "trigger" field that can be used to force delays while copying data. Everytime the device is done with a part of the transfer, it will wait for some condition to become true before going on. And that condition can be the end of a picture (vblank) or the end of a scan line (hblank of "horizontal blank", and this is where the H of HDMA comes from). By synchronizing my copy-to-palette on new lines, I'll get a nice, amiga-like, sunset raster in the background of my scene.

On retrouve dans les registres de contrôle DMAxCNT du GBA un champ 'trigger' qui permet à un canal de faire une pause entre deux écritures, cette pause s'arrêtant quand une certaine condition apparaît sur la machine. Parmi les conditions possibles, la fin d'une image ou la fin d'une ligne (vblank et hblank, respectivement). On y retrouve aussi des champs permettant de dire si les addresses mémoire doivent évoluer après chaque mot transféré. Si on veut par exemple reprogrammer un registre de scrolling à chaque ligne (pour émuler un effet de profondeur), on demandera que la source avance à chaque transfer (pour parcourir un tableau de valeurs) tout en restant à une adresse fixe pour la destination (le registre de scrolling, tiens!).

La nintendo DS reprend pour l'essentiel le mécanisme de DMA du GBA, sauf qu'on a 4 canaux pour l'ARM9 et 4 autres canaux pour l'ARM7. Seul l'ARM9 peut faire du H-DMA. Il peut aussi se synchroniser sur un nouveau type de trigger (j'aurais dû écrire "déclencheur" dès le départ): l'apparition de place dans la file de transfert vers le moteur 3D (le GXFIFO). La DS a aussi 16 canaux DMA supplémentaires pilotés par les registres sonores de l'ARM7, un pour chaque piste audio, mais ils sont totalement dédiés à cette fonction.

Des transferts DMA, j'en ai déjà fait pas mal dans mes homebrews, le plus souvent pour faire un 'memcopy boosté' entre la mémoire principale et la mémoire vidéo. Dans ce cas-là, le bit 'repeat' était à 0, et le canal s'auto-désactivait une fois le transfert terminé (c'est à dire que les N mots sont copiés). Pour notre 'effet de profondeur', on devrait programmer le canal DMA pour qu'il ne transfère qu'un seul mot (la nouvelle valeur de scrolling) au bon moment, mais activer le mode 'repeat', de sorte qu'un nouveau transfert ait lieu après chaque ligne.

The NintendoDS has DMA hardware very similar to that of the GBA, except it has 4 channels for each CPU, but only the ARM9 can do H-DMA. It can also synchronize on a newly introduced trigger type: room in the 3D FIFO queue. Oh, and while there are 16 more DMA channels to drive sample fetching for the sound hardware, they're controlled through the ARM7 sound registers and are hard-wired for that purpose. 

I already did a good deal of DMA transfers in my DS homebrews, mostly to boost memory copies between main and video memory. In that case, the 'repeat' bit was cleared and the DMA channel would auto-deactivate once the transfer was over (that is, once the N words configured were copied). For an HDMA effect, I would configure N=1, but enable the 'repeat' bit so that a new transfer is started at every scan line. That would work for my raster, or for underwater screen wobbling effect.

(Picture from RGMech Ex channel)
I could also reprogram multiple registers at every line, like coordinates of a window effect to get a curtain effect or a giant ink wave, provided that they would be contiguous in the address space. This is what the "advance and reload" destination mode is for. Everytime a word is written, the target address is incremented but when a next transfer starts (because of the 'repeat' bit), the contents of the destination address is reloaded from configuration registers. Oh, and likely, I'd have to manually reset the source address during the VBlank else it will end up using garbage values after a few frames. And that's it. Fairly simpler than the what the SNES hardware could do with its HDMA table and entries, but I guess the higher amount of RAM of the DS (and GBA?) made such 'run-length-encoded' techniques oversophisticated.

Supposons maintenant qu'on veuille reprogrammer plusieurs registres à chaque nouvelle ligne. Les positions de début et fin de fenêtre, par exemple, pour faire un effet de rideau plus dynamique ou une vague d'encre géante. On peut évidemment utiliser un canal DMA pour chacun, mais s'ils ont le bon goût d'être l'un après l'autre en mémoire, on peut utiliser le dernière mode de gestion de la destination: le mystérieux 'avance et recharge'. Ici on passe au registre suivant après chaque mot transféré, mais quand le transfert redémarre (parce qu'on a activé le bit 'repeat'), on repart du registre-destination qui avait été programmé, et pas de sa valeur à la fin du transfert précédent.

edit: ah oui. Bon évidemment, la 'source' du transfert a continué à augmenter tout au long de l'image, ligne après ligne, et si on ne fait rien, il continuera d'augmenter aussi pendant le 'vblank' (équivalent-lignes inactives après le bas de l'image). Il faudra le reprogrammer avec la nouvelle valeur d'origine (ou une autre) au moment d'attaquer la nouvelle image.




1 comment:

McMartin said...

I can't actually read French, but I do find it interesting that that article opens by calling HDMA "Blitter-like" -- while I was working with it the 16-bit graphics chip it reminded me much more of was the Copper, not the Blitter.