ASMtrad CPC

Apprenez l'assembleur Z80

DMA son

Avant toute chose, sachez que je n'aborderai que très brièvement les interruptions DMA dans ce chapitre.
Pour plus de renseignements à ce sujet, un article existe sur les interruptions sur CPC+/GX4000

Il est aussi préférable avant de lire cet article de connaitre le fonctionnement du PSG (Programmable Sound Generator) et de ses registres

Principe

Sur l'ancienne gamme des CPC, faire de la musique ou plus simplement un son demandait à adresser le PPI avant de sélectionner un registre du PSG et de lui envoyer une valeur.
13 registres étaient disponibles et il fallait donc utiliser pour chaque un minimum de deux OUT pour envoyer une valeur dedans.
Même si tous les registres ne sont pas utiles forcément à chaque fois, cela représente tout de même quelques instructions, sans compter l'adressage du PPI.

Sur CPC+ il a donc été décidé de se passer de tout ce système et d'accéder presque directement au processeur sonore.
Pour cela, le choix c'est porté sur des DMA (direct mémory access).
Ceci signifie simplement qu'un accès direct entre le processeur sonore (AY-3-8912), notre fameux PSG et la RAM est géré. Mais géré d'une façon nouvelle...

Plutôt que de faire le choix d'un accès au PSG sans passer par le PPI, les ingénieurs d'Amstrad ont décidé de mettre en place des DMA-list.
Véritable langage dans la machine, ces DMA liste sont lues en RAM centrale par l'ASIC qui ensuite envoies directement au PSG registres et valeurs.
Mais pas seulement... L'ASIC va aussi gérer des pauses, des boucles à répétitions et des interruptions dédiées

C'est donc un langage exclusif à l'ASIC et au son qui est mis en place, permettant d'alléger les accès au processeur sonore et automatisant les envois dans celui-ci

Le langage

Avant toute chose et avant toute explication surtout, voyons rapidement les quelques instructions qui composent les DMA-list:

Instructions DMA-list
InstructionValeursDescription
LOAD R,D#0RDDEnvoie de l'octet DD dans le Registre R du PSG.
PAUSE N#1NNNAttend N fois l'unité de pause.
N:0 à 4095.
PAUSE 0 est équivalent à NOP.
REPEAT N#2NNNInitialise le compteur de boucle à N (nombre de bouclages).
N:0 à 4095.
REPEAT 0 est équivalent à REPEAT 1.
NOP#4000Ne rien faire...
LOOP#4001Boucle.
Si le compteur de boucle est différent de zéro, alors il est décrémenté et on retourne à l'instruction suivant le REPEAT.
INT#4010Génère une interruption DMA à la lecture de cette instruction. Chaque DMA peut en générer une.
STOP#4020Fin d'AY-liste.
Mise à 0 du bit correspondant du registre DCSR (bit 0, 1 ou 2 selon le DMA en cours).

Comme vous pouvez le remarquer, chacune de ces instructions est codée sur 16bits (même le NOP).
La raison est simple: l'ASIC lira 2 octets à chaque HBL et par DMA.
La fréquence pour jouer un sample sera donc de 15625Hz si celui-ci est joué à chaque ligne (toutes les 64μs). Comme nous disposons de 3 canaux DMA, rien ne vous empèche de lire une DMA-list sur chacun d'entre eux.

- Les DMA-list doivent être placées sur une adresse paire.
- Les octets sont inversés en RAM. #4000 devient donc #00,#40
- Votre DMA-list doit se trouver OBLIGATOIREMENT en RAM centrale, l'ASIC n'ira pas lire ni dans la RAM supérieure, ni dans une ROM.
- Une DMA-list se termine obligatoirement par une instruction STOP.

Chose à noter aussi, les canaux DMA n'ont rien à voir avec les canaux A, B et C du processeur sonore.
Une DMA-list pourra tout aussi bien contenir des données à envoyer sur les 3 canaux.
Par exemple pour ma part il m'arrive d'utiliser une DMA-list uniquement pour générer une table d'interruptions raster...

Au niveau des instructions, l'instruction LOAD R,D n'a rien de spéciale. Vous y donnez le registre du PSG et la valeur à envoyer.

L'instruction NOP comme son nom l'indique ne fait rien. Mais elle sera très utile pour s'insérer entre des sintructions.

L'instruction INT provoque une interruption DMA au moment ou elle est lue. vous pourrez donc avoir des interruptions à la ligne facilement.
Mais aussi synchroniser des évenements par exemple avec votre musique.

L'instruction STOP signe l'arret d'une DMA-list. Quand l'instruction est rencontrée, la DMA-list est arrétée.

L'instruction LOOP permet de faire une boucle (tout simplement).
Cette sintruction est forcément couplée avec l'instruction REPEAT N
En effet, l'instruction REPEAT N initialise le compteur de boucle. Celui-ci est décrémenté à chaque fois que l'instruction LOOP est rencontrée.
La boucle s'effectue donc sur l'instruction suivant l'instruction REPEAT N.
Notez que pendant que vous êtes dans une boucle, vous pouvez en modifier le contenu. MAIS et faites-y attention car certains émulateur le gèrent mal: Si vous modifiez le REPEAT ceci ne sera pas pris en compte. En effet celui-ci n'est pas relu tant que vous êtes dans la boucle puisque celle-ci saute sur l'instruction d'après.
Notez enfin que l'instruction LOOP prend bien le temps d'une instruction (donc est équivalente à un NOP), c'est important car si vous voulez des timings précis dans vos boucles il faudra donc compter sur ce NOP de trop...
Enfin on regrettera le fait de ne pas pouvoir imbriquer des boucles, ce qui aurait entre autre permis de faire boucler une musique.

Comme nous l'avons vu, nous pouvons placer notre DMA-list ou nous le souhaitons tant qu'elle se trouve en RAM centrale (malheureusement).

Pour définir l'adresse de chaque DMA-list nous avons donc dans la page I/O ASIC une adresse ou poker celle-ci.
Notez que le bit 0 de l'adresse ne compte pas et n'est simplement pas pris en compte, ce qui est logique puisqu'une DMA-list commence forcément sur une adresse paire.

Définition des adresses des DMA-list
DMA n°ADRESSE (SAR)
0#6C00-#6C01
1#6C04-#6C05
2#6C08-#6C09

Unité de Pause: PPR

De plus, nous pourrons définir pour chaque DMA-list une unité de pause.
Cette unité de pause est en fait un simple compteur qui s'incrémente à chaque HBL.
Par exemple, si vous mettez l'unité de pause à 4, la valeur considérée sera: 0 puis à la HBL suivante: 1, puis 2, puis 3, puis 4.
Il y aura ensuite bouclage à 0.
Notez aussi que la valeur commence bien à 0.
Une instruction Pause avec une valeur d'unité de pause=0 sera équivalente à l'instruction NOP.

Les unités de pause sont définissables pour chaque canal DMA aux adresses suivantes:

Définition de l'unité de pause de chaque DMA.
DMA n°ADRESSE PPR
0#6C02
1#6C06
2#6C0A

Si l'on peut définir l'unité de pause avant de lancer une DMA-list, on peut aussi la modifier en court d'execution. MAIS ATTENTION !!!
On pourrait bêtement penser que si on envoie une nouvelle unité de pause, celle-ci sera prise en compte au prochain bouclage du compteur.
Ce n'est pas le cas. L'unité de pause est prise en compte immédiatement.

Aussi, si par exemple votre unité de pause était à 5 et que le compteur de celle-ci n'est arrivé qu'à 3 au moment ou vous changez l'unité de pause, la valeur suivante sera 0.
En effet, le compteur bouclera immédiatement et s'incrémentera ensuite jusqu'à la nouvelle valeur.

Contrôle des DMAs

Nous savons désormais donner l'adresse d'une DMA-list; son unité de pause et créer une DMA-list. Mais comment la lancer ? C'est ce que nous allons voir ici.

Les DMAs sont gérés par ce que l'on appelle le registre DCSR. Celui-ci est un registre de contrôle interne à l'ASIC et qui permet diverses choses dont lancer nos DMA-list.

En voici la description:

DCSR-DMA

Comme vous pouvez le voir c'est ici très simple, il suffit simplement de mettre le bit correspondant à 1 pour lancer une DMA-list.
Ce même bit peut-être lû et vous pourrez donc savoir si une DMA-list est active ou non.

Lorsqu'une DMA-list est lancée, rien ne vous empèche d'en changer l'adresse... Voir même de la changer de DMA.

Notez que si vous coupez une DMA-list et que vous la rallumez, elle reprendra la ou elle en était.

De même, après une instruction STOP, l'adresse de la DMA-list est donc celle juste après l'instruction. Il faut redonner l'adresse de la DMA-list si vous voulez la relancer.