Assembly Avanzato con MASM

Capitolo 15: Il DMAC 8237A - Programmable Direct Memory Access Controller


Nella sezione Assembly Base abbiamo visto che qualsiasi istruzione relativa alla piattaforma 80x86 viene eseguita sotto la supervisione della CPU; ciò è vero anche nel caso di trasferimenti di dati Memory to Memory come accade, ad esempio, con l'istruzione:
movsb
Apparentemente, questa istruzione legge un dato di tipo BYTE dall'indirizzo DS:SI e lo copia direttamente all'indirizzo ES:DI; in realtà, tale trasferimento dati comporta un passaggio intermedio attraverso un registro temporaneo della CPU. Il BYTE quindi viene letto da DS:SI, trasferito in un registro temporaneo a 8 bit della CPU e da qui copiato all'indirizzo ES:DI.

Nel caso di grossi trasferimenti di dati, la situazione appena descritta implica un intenso utilizzo della CPU per un tempo relativamente lungo; ciò pone seri problemi di efficienza per qualunque SO.
Del resto, risulta anche evidente l'inutilità della CPU nel corso di un generico trasferimento di dati; tutto ciò che si deve fare consiste nello spostare un gruppo di bit, da una sorgente ad una destinazione, senza la necessità che il microprocessore debba svolgere alcuna elaborazione.

Proprio queste considerazioni hanno spinto i progettisti dei computer a studiare una soluzione capace di garantire lo svolgimento di grossi trasferimenti di dati con un coinvolgimento minimo della CPU; in conseguenza di tali studi, nasce un dispositivo che, sui primi PC della famiglia 80x86, prende il nome di DMA Controller o DMAC.
L'acronimo DMA sta per Direct Memory Access (accesso diretto alla memoria) e indica il fatto che, attraverso un apposito controller, le periferiche del computer possono scambiare blocchi di dati accedendo direttamente in memoria senza "disturbare" la CPU. Solo le periferiche che supportano il DMA possono sfruttare questa possibilità; una particolare periferica che supporta il DMA è la stessa RAM, in modo da rendere possibili i cosiddetti "trasferimenti di dati Memory to Memory".

Il principio di funzionamento di un DMAC è molto semplice:

15.1 Evoluzione dell'hardware per l'accesso diretto alla memoria

I primi PC di classe XT della IBM sono caratterizzati da una architettura denominata ISA (Industry Standard Architecture); tale architettura prevede una gestione centralizzata dell'accesso diretto in memoria attraverso un apposito chip che, nella versione iniziale, è il celebre Intel 8237A High Performance Programmable DMA Controller (nel seguito indicato brevemente come DMAC 8237A). La Figura 15.1 illustra l'interfaccia tra un DMAC 8237A e il resto del sistema. Osservando la Figura 15.1 si intuisce subito che il DMAC 8237A è stato progettato per interfacciarsi con la CPU Intel 8080, dotata di Data Bus a 8 linee e Address Bus a 16 linee, capace di indirizzare solo 216=64 KiB di RAM (da 0000h a FFFFh); tutto ciò ci fa capire perché questo chip sia diventato ben presto obsoleto.

Il DMAC 8237A è in grado di gestire trasferimenti di dati a 8 bit; un blocco di dati cioè, viene trasferito a gruppi di 8 bit alla volta. La frequenza di trasferimento dati è di circa 1.6 MiB/s ed è limitata dalla frequenza del segnale di clock a 5 MHz che il controller utilizza per scandire il suo ritmo di lavoro interno.
A causa delle limitazioni della CPU 8080 (indirizzamenti a 16 bit), il DMAC 8237A è nato per trasferire blocchi di memoria di dimensione massima pari a 64 KiB; si tratta infatti della quantità massima di memoria indirizzabile con quel modello di microprocessore.
Per interfacciarsi all'Address Bus a 16 linee della 8080, il DMAC 8237A genera direttamente gli 8 bit meno significativi dell'indirizzo a 16 bit; come si vede in Figura 15.1, gli 8 bit più significativi vengono generati attraverso il Data Bus interno e poi aggiunti all'indirizzo finale mediante un dispositivo esterno denominato 8-bit latch.

Sempre dalla Figura 15.1 si può notare che il DMAC 8237A è dotato di 4 ingressi DREQ (0, 1, 2 e 3) e 4 uscite DACK (0, 1, 2 e 3), che rappresentano (in coppia) i cosiddetti DMA channels (canali DMA); attraverso tali canali è possibile connettere sino a 4 periferiche differenti, le quali risultano così abilitate a richiedere l'accesso diretto alla memoria tramite il DMAC.
Come accade per i PIC 8259 (Figura 3.3 del Capitolo 3), anche i DMAC 8237A possono essere collegati in cascata; in questo modo è possibile aumentare il numero di canali DMA in base alle esigenze.

Servendoci della Figura 15.1, vediamo come si svolge un tipico esempio di accesso diretto alla memoria da parte di una periferica.
Innanzi tutto, il programmatore deve predisporre i dati che saranno letti (ad esempio, dalla scheda audio) o scritti (ad esempio, dall'hard disk) direttamente in memoria; il passo successivo consiste nel programmare, sia il DMAC, sia la periferica coinvolta nel trasferimento dati, specificando anche l'indirizzo di lettura o di scrittura in RAM e il numero di BYTE da trasferire (contatore).
Ipotizziamo, ad esempio, che l'hard disk debba trasferire un blocco di dati direttamente in memoria; una volta che il controller dell'hard disk e il DMAC sono stati opportunamente programmati, il conseguente trasferimento dati si svolge nel seguente modo: Generalmente, una volta che il blocco dati è stato trasferito, la periferica che aveva richiesto l'accesso diretto in memoria produce una IRQ a cui la CPU associa una INT n; la ISR che intercetta l'interruzione può così prendere gli opportuni provvedimenti (come, ad esempio, riprogrammare la periferica e il DMAC nel caso in cui si debbano trasferire ulteriori blocchi di dati).

Già con l'arrivo della CPU 8086, con Data Bus a 16 linee e Address Bus a 20 linee, capace di indirizzare quindi sino a 220=1 MiB di RAM (da 00000h a FFFFFh), le limitazioni imposte dal DMAC 8237A cominciano a rendersi evidenti, soprattutto per quanto riguarda gli indirizzi a soli 16 bit; per aggirare tale limite, vengono aggiunti quattro dispositivi esterni di tipo 4-bit latch, uno per ogni canale DMA, capaci di estendere un indirizzo, da 16 bit a 16+4=20 bit.
Il problema che si presenta è dato dal fatto che i 4 bit più significativi dell'indirizzo a 20 bit, gestiti dal 4-bit latch esterno, non vengono automaticamente incrementati nel caso in cui si verifichi un riporto dai 16 bit meno significativi; ad esempio, se l'indirizzo iniziale a 20 bit del blocco da trasferire è 2FFFFh, sommando 1 si ottiene 20000h anziché 30000h. Di conseguenza, permane la limitazione a 64 KiB della dimensione massima del blocco dati da trasferire (si può andare cioè da X0000h a XFFFFh senza che si verifichi il wrap around); eventuali blocchi di dimensioni maggiori devono essere suddivisi in sotto-blocchi, ciascuno dei quali non può superare 64 KiB.

La situazione appena descritta permane con l'arrivo dei primi PC di classe AT, equipaggiati con CPU 80286 dotata di Data Bus a 16 linee e Address Bus a 24 linee, capace di indirizzare quindi sino a 224=16 MiB di RAM (da 000000h a FFFFFFh); ad ogni canale DMA viene aggiunto un 8-bit latch in modo da estendere un indirizzo, da 16 bit a 16+8=24 bit.
La vera novità introdotta sui PC di classe AT è che i soli 4 canali DMA disponibili cominciano ad essere insufficienti, per cui viene aggiunto un secondo DMAC 8237A collegato in cascata al primo, come si vede in Figura 15.2. La situazione è del tutto simile al collegamento in cascata di due PIC 8259 (Figura 3.3 del Capitolo 3); la differenza fondamentale sta nel fatto che stavolta, il primo DMAC svolge il ruolo di Slave, mentre il secondo rappresenta il Master.
Il DMAC Slave si occupa quindi dei canali 0, 1, 2 e 3, mentre il DMAC Master ha in carico i canali 4, 5, 6 e 7; risulta possibile così collegare sino a 7 periferiche in quanto, come si vede in Figura 15.2, il channel 4 è destinato alla connessione in cascata.

Una ulteriore novità è che, mentre il DMAC Slave continua a gestire trasferimenti di dati a 8 bit, il DMAC Master supporta invece trasferimenti di dati a 16 bit; a tale proposito, si ricorre ad una tecnica denominata fly-by che permette di aggirare la limitazione del Data Bus interno a soli 8 bit. Normalmente, durante un trasferimento dati a 8 bit, i vari BYTE viaggiano dalla sorgente alla destinazione transitando nel DMAC; in modalità fly-by, invece, le varie WORD viaggiano direttamente dalla sorgente alla destinazione attraverso il Data Bus esterno (a 16 o più bit), senza passare nel DMAC.
Per tenere traccia del numero di WORD trasferite, si ricorre ad un artificio (illustrato più avanti) attraverso il quale, ad ogni decremento di 1 del contatore interno del DMAC, corrisponde un incremento di 2 dell'indirizzo a cui accedere (in pratica, vengono conteggiate le WORD e non i BYTE). Nonostante sia quindi possibile, in teoria, conteggiare 65536 WORD, pari a 131072 BYTE, rimane ancora una volta la limitazione dei 64 KiB; in sostanza, per evitare il wrap around, un indirizzo a 24 bit può andare da XX0000h a XXFFFFh, con 65536 incrementi di 1 sui canali a 8 bit o con 32768 incrementi di 2 sui canali a 16 bit.

All'epoca dei PC di classe AT, i 7 canali DMA risultano assegnati come illustrato in Figura 15.3. Questi assegnamenti sono destinati a durare poco, visto che le nuove CPU 80386 e 80486, insieme ai relativi bus di sistema, permettono di lavorare con frequenze di trasferimento dati proibitive per il DMAC 8237A; inoltre, questi nuovi microprocessori offrono un Address Bus a 32 linee, con la possibilità di indirizzare quindi sino a 232=4 GiB di RAM (da 00000000h a FFFFFFFFh), mentre il DMAC 8237A rimane ancorato ai 16 MiB gestibili con una CPU 80286 (non vengono aggiunti ulteriori n-bit latch).
Il channel 0 viene così liberato in quanto il DMAC 8237A si rivela del tutto inadeguato per gestire il refresh delle nuove e sempre più veloci memorie DRAM; al suo posto viene introdotto l'uso di appositi chip espressamente dedicati a svolgere tale compito.
Anche il channel 3 viene liberato in quanto i nuovi hard disk, oltre ad essere sempre più veloci, sono in grado di supportare i trasferimenti di dati a 32 bit; per gestire tali periferiche, si ricorre allora ad altri metodi come PIO (Programmed Input/Output) e UDMA (Ultra DMA).

Considerando il fatto che il channel 5 viene usato solo sulle architetture PS/2, l'avvento delle CPU a 32 bit ha portato un cambiamento negli assegnamenti dei canali DMA come illustrato in Figura 15.4. Per compatibilità con gli standard IBM, il channel 2 risulta cablato in modo fisso (hard wired) al controller del floppy disk; ciò viene dato per scontato anche se tale periferica non è presente fisicamente.
Tra le poche altre periferiche che in quel periodo continuano ad usare il DMAC 8237A troviamo le schede audio; nel precedente capitolo, ad esempio, abbiamo visto che, in accordo con gli assegnamenti di Figura 15.4, la Sound Blaster 16 utilizza i canali 0, 1, 3 per il trasferimento dati a 8 bit e 5, 6, 7 per il trasferimento dati a 16 bit.

Nel corso degli anni sono state introdotte versioni più evolute del DMAC, ma tutte hanno mantenuto la compatibilità con il DMAC 8237A; successivamente si è passati all'integrazione del DMAC 8237A direttamente nella motherboard dei PC, con lo scopo di garantire la compatibilità con il vecchio hardware.

Nell'architettura PCI (Peripheral Component Interconnect), successiva alla ISA, il concetto di gestione centralizzata del DMA viene messo da parte; ogni periferica PCI è dotata di un proprio DMAC autonomo con il quale può richiedere al PCI Bus Controller l'autorizzazione per accedere direttamente alla memoria. Il PCI Bus Controller si limita a "gestire il traffico" nel caso in cui più periferiche stiano richiedendo l'accesso diretto in memoria; solo una periferica per volta può avere l'autorizzazione e, in tal caso, si dice che essa diventa "Bus Master".

Dalle considerazioni appena esposte si può notare che, durante il trasferimento dati, la CPU cede il controllo di tutti i bus di sistema al DMAC; apparentemente, quindi, in questa fase la CPU stessa deve restare inattiva in attesa che il DMAC completi il proprio lavoro. Per ovviare a questo problema si ricorre a diverse tecniche.
In Burst Mode, il controller del DMA si impadronisce di tutti i bus di sistema finché il trasferimento di un blocco di dati non è terminato; in tale modalità, effettivamente, la CPU è costretta a restare inattiva anche per lunghi periodi di tempo, garantendo così al DMAC la massima velocità possibile nell'eseguire il proprio lavoro, ma con gravi ripercussioni sull'efficienza generale del computer.
In Cycle Stealing Mode, la CPU e il DMAC si alternano continuamente nel possesso dei bus di sistema; generalmente, la CPU esegue un'istruzione, poi cede il controllo al DMAC il quale trasferisce un singolo BYTE (o WORD) e cede a sua volta il controllo di nuovo alla CPU in modo che i passi precedenti vengano ripetuti per il numero necessario di volte. In questa modalità il trasferimento dati è più lento del Burst Mode, ma viene garantita la continua operatività (anche se a velocità ridotta) della CPU.
In Transparent Mode, il DMAC svolge il proprio lavoro solo quando la CPU sta eseguendo operazioni che non comportano l'uso dei bus di sistema; con questo metodo, il trasferimento dati è anche più lento del Cycle Stealing Mode, ma l'efficienza complessiva del computer è massima in quanto la CPU può continuare ad operare senza limiti di tempo.

Un'ultima considerazione riguarda l'uso della tecnica del DMA con CPU dotate di Cache Memory; come sappiamo, in tale memoria la CPU carica quei dati e istruzioni, presenti nella RAM, che vengono acceduti più frequentemente durante le elaborazioni.
L'uso combinato di Cache Memory e DMA può portare ad un problema indicato con il nome di Cache Coherence. Supponiamo che la CPU abbia caricato nella cache un dato di nome V1; tutte le elaborazioni su tale dato si ripercuoteranno sulla versione di V1 presente nella cache stessa. Nel frattempo, al DMAC potrebbe arrivare una richiesta per trasferire un blocco dati, tra cui V1, dalla RAM ad una periferica; ciò che viene trasferito quindi è la vecchia copia di V1 (quella presente in RAM) e non la versione aggiornata presente nella cache.
Analogamente, il DMAC potrebbe aggiornare il valore di V1 in seguito ad un trasferimento dati da una periferica alla RAM; in tal caso, sarebbe la CPU ad operare su un vecchio valore di V1 (quello presente nella cache).
Per risolvere questo problema, si ricorre a tecniche hardware che permettono di sincronizzare la gestione dei dati tra cache e aree della RAM soggette ad operazioni di accesso diretto alla memoria; in assenza di supporto hardware, è compito dei SO affrontare tale problema via software.

15.2 Struttura interna e funzionamento del DMAC 8237A

Analizziamo più in dettaglio il funzionamento del DMAC 8237A; la Figura 15.5 mostra uno schema a blocchi relativo alla struttura interna del dispositivo. Il DMAC 8237A dispone di 344 bit di memoria interna, distribuita tra vari registri; la Figura 15.6 illustra i nomi di tali registri, la loro ampiezza in bit e la loro quantità. Il blocco TIMING AND CONTROL di Figura 15.5 utilizza l'ingresso CLK per scandire il ritmo di lavoro interno del chip; esso provvede anche a generare i vari segnali di controllo esterni.
Il blocco COMMAND CONTROL decodifica i vari comandi inviati al DMAC 8237A attraverso la CPU; inoltre, si occupa anche di decodificare la Mode Control Word mediante la quale si può impostare il tipo di accesso diretto alla memoria.
Il blocco PRIORITY ENCODER gestisce le varie DMA Requests provenienti dalle periferiche, disponendole in ordine di priorità; esso permette anche di modificare le impostazioni predefinite per la priorità di ogni canale. Come per il PIC 8259, le impostazioni predefinite prevedono che la priorità sia decrescente al crescere del numero del canale; quindi, la priorità massima viene assegnata al channel 0, mentre la minima viene assegnata al channel 4.

Il DMAC 8237A può trovarsi in due "cicli operativi": Idle (inattivo) e Active (attivo); ciascuno di tali cicli è formato a sua volta da un certo numero di stati. Sono possibili 7 stati differenti; ciascuno di essi richiede un tempo pari ad un periodo del segnale di clock.
Se il DMAC è nel ciclo Idle, esso assume solo lo stato SI (stato inattivo); ciò accade quando non è presente alcuna DMA Request da parte delle periferiche. Nello stato SI, il DMAC può essere programmato attraverso la CPU; si dice allora che esso si trova in Program Condition.
Non appena arriva una DMA Request, il DMAC attiva la linea HRQ e si porta nello stato S0 in attesa che la CPU risponda attraverso la linea HLDA; è ancora possibile programmare il DMAC mentre esso è nello stato S0.
Non appena la CPU risponde attivando la linea HLDA, il DMAC acquisisce il controllo di tutti i bus di sistema e avvia il trasferimento dati assumendo, in sequenza, gli stati S1, S2, S3 e S4; se il trasferimento richiede più tempo del previsto, il DMAC può inserire un certo numero di stati di attesa (SW o Wait States) tra S3 e S4. Il trasferimento dati Memory to Memory richiede la lettura di un blocco dati da un indirizzo della RAM e la successiva scrittura del blocco stesso in un altro indirizzo della RAM; in tal caso, per ogni BYTE (o WORD) da trasferire, il DMAC assume otto stati complessivi (due gruppi da quattro stati ciascuno). Per distinguere le due fasi, si utilizza la notazione S11, S12, S13, S14 per la fase di lettura e S21, S22, S23, S24 per la fase di scrittura.

Abbiamo visto che nel ciclo Idle, il DMAC 8237A passa allo stato inattivo SI; in tale stato il dispositivo verifica continuamente gli ingressi DREQ per sapere se qualche periferica sta effettuando una DMA Request. Sempre nello stato SI, il DMAC controlla continuamente l'ingresso CS (chip select) attraverso il quale la CPU segnala di voler intervenire per leggere o scrivere nei registri del dispositivo (cioè, per programmare il DMAC); trattandosi di un ingresso complementato (CS=NOT(CS)), esso vale 1 quando è inattivo e diventa 0 quando la CPU vuole intervenire (CS=0 implica CS=1 e viceversa).
Quando CS=0 e HLDA=0, il DMAC si trova nella tipica condizione che consente la sua programmazione (Program Condition); in tal caso, le quattro linee bidirezionali da A0 a A3 dell'Address Bus interno vengono usate come input per selezionare il registro a cui vogliamo accedere, mentre le linee bidirezionali IOR e IOW vengono usate come input per stabilire se si deve effettuare una lettura o una scrittura nel registro selezionato.

A causa del Data Bus interno a 8 bit del DMAC 8237A, per gestire la programmazione dei vari registri ADDRESS e WORD COUNT a 16 bit, viene usato un apposito Flip-Flop interno; la Figura 15.7 mostra un generico Flip-Flop Set/Reset, il cui funzionamento è stato già illustrato nella sezione 8.2 del Capitolo 8 (tutorial Assembly Base). Il Data Bus interno a 8 bit impone che l'accesso in scrittura ai registri a 16 bit del DMAC 8237A avvenga un BYTE alla volta; il compito del Flip-Flop (che, come sappiamo, può assumere i due stati stabili 0 o 1) è quello di cambiare stato ad ogni accesso in modo da far puntare alternativamente il Data Bus interno, al BYTE basso (LSB) e al BYTE alto (MSB) del registro a 16 bit a cui si sta accedendo.
Il problema che si presenta è dato dal fatto che il DMAC 8237A dispone di vari registri a 16 bit e di un unico Flip-Flop; di conseguenza, tale unico Flip-Flop viene usato per far puntare alternativamente il Data Bus interno, al BYTE basso e al BYTE alto di tutti i registri a 16 bit!
Considerando che vi possono essere sino a quattro periferiche connesse al DMAC, bisogna evitare allora il rischio che il Flip-Flop si venga a trovare in uno stato indefinito; in sostanza, quando arriva il nostro turno, non sappiamo in che condizione (0 o 1) sia stato lasciato il Flip-Flop dalla precedente periferica che ha richiesto un accesso diretto alla memoria.
La soluzione migliore, nel momento in cui stiamo riprogrammando il DMAC, consiste allora nel resettare il Flip-Flop, in modo da far puntare il Data Bus interno al BYTE basso di tutti i registri a 16 bit; a tale proposito, come vedremo più avanti, si può ricorrere ad appositi comandi.

15.2.1 Modalità di trasferimento dati

Non appena viene rilevata una DMA Request su una delle quattro linee DREQ, il DMAC 8237A passa dal ciclo Idle al ciclo Active e invia una HRQ alla CPU; quando la CPU risponde inviando una HLDA, il DMAC ottiene il possesso di tutti i bus e può avviare il trasferimento dati secondo una delle quattro possibili modalità descritte nel seguito. In questa modalità, non appena riceve una HLDA dalla CPU, il DMAC 8237A acquisisce tutti i bus di sistema, trasferisce un singolo BYTE (o WORD) di dati, decrementa di 1 il contatore interno, incrementa (o decrementa) di 1 (o di 2, per le WORD) il registro indirizzi; infine, disattiva la linea HRQ in modo che il controllo dei bus di sistema torni alla CPU. La CPU esegue la prossima istruzione e poi riceve una nuova HRQ dal DMAC; quando il DMAC riceve una HLDA dalla CPU, procede al trasferimento di un altro BYTE (o WORD) di dati.
Questa situazione si ripete continuamente finché il contatore non raggiunge lo zero; a questo punto, il DMAC attiva la linea TC (terminal count).

Questa modalità di trasferimento dati corrisponde a quella che in precedenza è stata definita Cycle Stealing Mode; si tratta quindi di una modalità relativamente lenta, adatta al trasferimento di piccoli blocchi di dati. In questa modalità, non appena riceve una HLDA dalla CPU, il DMAC 8237A acquisisce tutti i bus di sistema e avvia il trasferimento di un intero blocco di dati (max. 64 KiB); per ogni BYTE (o WORD) trasferito, il contatore interno viene decrementato di 1, mentre il registro indirizzi viene incrementato (o decrementato) di 1 (o di 2, per le WORD).
Quando il contatore interno raggiunge lo zero, il DMAC termina il trasferimento dati e attiva le linee TC (terminal count) e EOP (End Of Process); lo stesso trasferimento dati può essere interrotto in qualsiasi momento attraverso un segnale esterno inviato al DMAC sulla linea bidirezionale EOP.

Questa modalità di trasferimento dati corrisponde a quella che in precedenza è stata definita Burst Mode; si tratta quindi di una modalità relativamente veloce, adatta al trasferimento di grossi blocchi di dati. Bisogna ricordare, però, che in questa modalità la CPU è costretta a restare inattiva anche per lunghi periodi di tempo. In questa modalità, la periferica che intende accedere direttamente in memoria, utilizza la sua linea DREQ per richiedere i servizi del DMAC in modo discontinuo (on-demand); la periferica può interrompere in qualsiasi momento il trasferimento dati disattivando la sua linea DREQ, per poi riprenderlo riattivando la stessa linea DREQ.
Mentre la linea DREQ è inattiva, la CPU riprende il controllo totale dei bus per eseguire una o più istruzioni; in ogni caso, quando il contatore interno raggiunge lo zero, il DMAC termina il trasferimento dati e attiva le linee TC e EOP. Il trasferimento dati può essere interrotto in qualsiasi momento attraverso un segnale esterno inviato al DMAC sulla linea bidirezionale EOP.

Questa modalità di trasferimento dati corrisponde a quella che in precedenza è stata definita Transparent Mode; si tratta quindi di una modalità anche più lenta della Single Transfer Mode, che però permette alla CPU di continuare ad operare senza eccessive limitazioni di tempo. Questa modalità viene utilizzata per gestire il collegamento in cascata di due o più DMAC 8237A, come illustrato in Figura 15.2; il livello di priorità di ogni canale viene preservato e si propaga quindi da un DMAC all'altro attraverso i vari PRIORITY ENCODER.
Il canale utilizzato per il collegamento in cascata non può generare alcun indirizzo o segnale di controllo; le sue linee DREQ e DACK gestiscono le varie DMA Requests provenienti dalle linee HRQ e HLDA del DMAC a cui sono connesse.

Sui PC di classe AT, come si vede in Figura 15.2, una DMA Request che giunge al DMAC Slave attraverso i canali 0, 1, 2 o 3, viene messa in ordine di priorità dal relativo PRIORITY ENCODER e poi passata sulla linea HRQ che è connessa al canale 4 del DMAC Master (DREQ0); il DMAC Master, a sua volta, mette la stessa DMA Request in ordine di priorità mediante il proprio PRIORITY ENCODER e poi, al momento opportuno, passa la richiesta sulla sua linea HRQ che è connessa alla CPU (HOLD). Dopo aver ricevuto una HLDA dalla CPU, il DMAC Master attiva la sua linea DACK0 che è connessa alla linea HLDA del DMAC Slave; a questo punto, il DMAC Slave attiva la propria linea DACK connessa alla periferica che ha inviato la richiesta. Il DMAC Slave può ora avviare il trasferimento dati o può lasciare questo compito alla periferica; in questo secondo caso, la periferica stessa ha la responsabilità di gestire indirizzamenti e segnali di controllo

15.2.2 Tipi di trasferimento dati

Abbiamo visto quindi che il DMAC 8237A dispone di tre modalità di trasferimento dati attive (la Cascade Mode viene usata solo per il collegamento in cascata); per ciascuna di queste tre modalità attive, sono disponibili tre differenti tipi di trasferimento dati denominati: Read, Write e Verify.

Il tipo Read prevede che un blocco dati venga letto dalla RAM e trasferito alla periferica che ha richiesto il servizio; a tale proposito, il DMAC attiva le linee MEMR (Memory Read) e IOW (I/O Write = scrittura in una periferica).

Il tipo Write prevede che un blocco dati venga letto dalla periferica che ha richiesto il servizio e trasferito alla RAM; a tale proposito, il DMAC attiva le linee IOR (I/O Read = lettura da una periferica) e MEMW (Memory Write).

Il tipo Verify, come si intuisce dal nome, è uno pseudo-trasferimento che permette di simulare i precedenti tipi Read o Write; il DMAC provvede ad aggiornare il contatore interno e il registro indirizzi, ma non effettua alcun reale trasferimento (le linee di controllo MEM R/W e IO R/W restano inattive).

15.2.3 Trasferimento dati "Memory to Memory"

Il DMAC 8237A permette di effettuare anche trasferimenti di dati di tipo Memory to Memory (da RAM a RAM); a tale proposito, come vedremo più avanti, è necessario porre a 1 l'apposito bit del registro COMMAND. Per il trasferimento vengono utilizzati i due canali predefiniti 0 e 1; il canale 0 è la sorgente, mentre il canale 1 è la destinazione.
Dopo aver inizializzato i registri BASE ADDRESS dei canali 0 e 1 e il registro BASE WORD COUNT del canale 1, si invia una DREQ al canale 0; il DMAC invia una HRQ alla CPU e, dopo aver ricevuto una HLDA, dà inizio al trasferimento dati.
Ogni BYTE letto dalla sorgente viene scritto nel registro TEMPORARY del DMAC; successivamente, tale BYTE viene letto dal registro TEMPORARY e scritto nella destinazione. Per ogni BYTE trasferito, vengono aggiornati i registri CURRENT ADDRESS dei canali 0 e 1; analogamente, il registro CURRENT WORD COUNT del canale 1 viene decrementato di 1.
Quando il contatore raggiunge lo zero, il DMAC attiva la linea TC; di conseguenza, viene attivata la linea EOP che termina il servizio.
Durante il trasferimento dati, è possibile attivare dall'esterno la linea EOP in modo da provocare una interruzione prematura del servizio.

15.2.4 Auto-inizializzazione

In base a quanto è stato esposto nella sezione 15.2.1, si può affermare che il comportamento predefinito del DMAC 8237A consiste nell'effettuare il trasferimento di un blocco dati in un'unica fase; si programmano la sorgente e la destinazione, si impostano gli indirizzi e il contatore e infine si dà il via al servizio. Quando il conteggio raggiunge lo zero, il trasferimento del blocco dati (max. 64 KiB) è terminato; si parla allora di Single Cycle Mode.
Se dobbiamo trasferire più blocchi di dati consecutivamente, al termine del trasferimento di ogni blocco siamo costretti a riprogrammare il DMAC e la periferica che ha richiesto il servizio; per evitare questo fastidio, il DMAC 8237A ci mette a disposizione un'altra modalità definita Auto Init Mode.
Come vedremo più avanti, ponendo a 1 un apposito bit del registro MODE, si fa in modo che il canale selezionato sul DMAC operi in una modalità detta Autoinit; in tale modalità, al termine del trasferimento di un blocco dati, l'attivazione della linea EOP fa in modo che i registri CURRENT ADDRESS e CURRENT WORD COUNT vengano automaticamente ricaricati con i valori iniziali che avevamo memorizzato nei registri BASE ADDRESS e BASE WORD COUNT. A questo punto, viene avviato in automatico il trasferimento del successivo blocco dati senza alcun intervento da parte della CPU.
Per il programmatore, questa tecnica è più impegnativa della Single Cycle, ma chiaramente si rivela molto più vantaggiosa in determinate circostanze; pensiamo, ad esempio, alla necessità di eseguire un file audio memorizzato su disco. Per gestire una situazione del genere, carichiamo il primo blocco dati in un buffer di memoria e impostiamo la modalità Autoinit; al termine del trasferimento del blocco dati, il DMAC informa la scheda audio la quale, a sua volta, genera una IRQ. La CPU associa una INT n a tale IRQ e la relativa ISR, una volta intercettata la richiesta di interruzione, non deve fare altro che caricare nel buffer di memoria il blocco dati successivo; a questo punto, il DMAC in automatico riprogramma il canale DMA che stiamo usando e avvia il nuovo trasferimento dati.

15.2.5 Gestione delle priorità

Ogni canale del DMAC 8237A ha una sua priorità rispetto agli altri; come è stato spiegato in precedenza, la priorità decresce al crescere del numero di canale, per cui, il channel 0 ha la priorità massima, mentre il channel 1 ha la priorità minima.
Le impostazioni appena illustrate sono quelle predefinite e si parla in tal caso di Fixed Priority; con questo tipo di impostazioni, se più canali richiedono un servizio, viene servito prima sempre il canale con la priorità più alta. Come si può facilmente immaginare, in Fixed Priority, se il canale con la priorità più elevata richiede più servizi consecutivi, esso finirà per bloccare tutti gli altri canali con priorità più bassa; proprio per questo motivo, esiste la possibilità di modificare via software le impostazioni predefinite, in modo da porre il DMAC 8237A in una modalità detta Rotating Priority. In Rotating Priority, quando un canale richiede un servizio, al momento opportuno esso viene servito e poi gli viene assegnata la priorità minima; le priorità degli altri tre canali vengono fatte ruotare di conseguenza.
Supponiamo di partire dalle priorità predefinite 0 (max.), 1, 2, 3 (min.); se ora, ad esempio, i canali 1 e 3 richiedono un servizio, il canale 3 viene messo in attesa, mentre il canale 1 viene servito e poi gli viene assegnata la priorità minima, con le priorità degli altri tre canali che ruotano di conseguenza, in modo da avere alla fine: 2 (max.), 3, 0, 1 (min.). A questo punto, arriva il turno del canale 3, il quale viene servito e poi gli viene assegnata la priorità minima; il nuovo ordine di priorità diventa quindi: 0 (max.), 1, 2, 3 (min.). E così via.

15.2.6 Temporizzazione compressa

In precedenza abbiamo visto che il DMAC 8237A trasferisce un singolo BYTE (o WORD) attraverso almeno quattro fasi rappresentate dagli stati S1, S2, S3 e S4; ciascuno di tali stati richiede un tempo pari ad un periodo del segnale di clock.
Come è stato già spiegato, durante lo stato S1, viene utilizzato il dispositivo 8-bit latch di Figura 15.1 per generare gli 8 bit più significativi dell'indirizzo a 16 bit da caricare sull'Address Bus.
Durante lo stato S2, il nuovo indirizzo a 16 bit appena generato viene caricato sull'Address Bus.
Durante lo stato S3, il DMAC verifica se sia necessario inserire degli stati di attesa SW prima che venga effettuata una lettura o scrittura.
Durante lo stato S4, viene effettuata l'effettiva lettura o scrittura del prossimo BYTE (o WORD) da trasferire.

Considerando appunto il fatto che su sistemi molto lenti potrebbe essere necessario inserire anche degli stati di attesa (SW), risulta che il trasferimento di un singolo BYTE (o WORD) richiede quattro o più cicli di clock; per evitare una situazione del genere, sui sistemi sufficientemente potenti è possibile impostare un apposito bit del registro COMMAND per richiedere al DMAC 8237A di operare in una modalità di funzionamento detta Compressed Timing.
In modalità Compressed Timing, il DMAC 8237A sopprime lo stato S3 ed esegue il trasferimento di ogni BYTE (o WORD) nei soli due stati S2 e S4; lo stato S1 è ancora presente ma, come è stato illustrato in precedenza, esso si rende necessario solo quando l'aggiornamento del registro CURRENT ADDRESS coinvolge anche il MSB dell'indirizzo a 16 bit da caricare sull'Address Bus.

15.2.7 Registri del DMAC 8237A

Analizziamo ora in dettaglio i registri illustrati in Figura 15.6. Il DMAC 8237A possiede quattro registri CURRENT ADDRESS a 16 bit, uno per ogni canale; tali registri sono destinati a contenere l'indirizzo corrente a cui il DMAC deve accedere direttamente in RAM durante un trasferimento dati richiesto da una periferica connessa al corrispondente DMA channel. La semplice struttura di un registro CURRENT ADDRESS è illustrata in Figura 15.8. La programmazione dei registri CURRENT ADDRESS deve essere effettuata quando il DMAC si trova in Program Condition; ciò accade, come sappiamo, nello stato SI del ciclo Idle o nello stato S0 del ciclo Active.
Una volta che abbiamo resettato il Flip-Flop, un accesso in scrittura al registro interessato ci permetterà di memorizzare il BYTE basso (LSB) dell'indirizzo; tale scrittura, provoca il cambiamento di stato del Flip-Flop, per cui un successivo accesso in scrittura al registro stesso ci permetterà di memorizzare il BYTE alto (MSB) dell'indirizzo.
Gli indirizzi di porta dei registri CURRENT ADDRESS sono illustrati in Figura 15.9. Chiaramente, un registro CURRENT ADDRESS è destinato a contenere un vero indirizzo fisico a 16 bit e non un indirizzo logico; ciò ha senso per le CPU 8080, con Address Bus a 16 linee, capace di indirizzare sino a 216=65536 byte di RAM (da 0000h a FFFFh).
Con l'avvento delle CPU 8086, con Address Bus a 20 linee, capace di indirizzare sino a 220=1048576 byte di RAM (da 00000h a FFFFFh), si rende necessaria la gestione degli indirizzamenti a 20 bit con il DMAC 8237A; abbiamo visto che questo problema è stato risolto con l'aggiunta di un 4-bit latch esterno per ogni canale, in modo da avere registri CURRENT ADDRESS da 16+4=20 bit. La Figura 15.10 mostra la nuova situazione, con il registro del 4-bit latch colorato in giallo. Come è facile verificare, i 4 bit più significativi di questo indirizzo fisico a 20 bit, rappresentano uno dei possibili 1048576/65536=16 blocchi da 64 KiB in cui risulta suddiviso l'unico MiB di RAM indirizzabile dalla CPU 8086; ciascuno di tali blocchi prende il nome di pagina di memoria, da cui il nome PAGE Register associato ad ogni 4-bit latch.
Osserviamo che, partendo da 00000h, abbiamo la pagina 0h di memoria, mentre i primi 16 bit possono andare da 0000h a FFFFh; ci stiamo muovendo quindi all'interno del primo blocco da 64 KiB di RAM. Se ora aggiungiamo 1 a FFFFh otteniamo 0000h con riporto di 1, per cui il nuovo indirizzo sarà 10000h, che rappresenta l'offset 0000h nella pagina di memoria 1h; all'interno di questo secondo blocco da 64 KiB possiamo muoverci, come al solito, da 0000h a FFFFh. E così via per le pagine 2h, 3h, etc.
Purtroppo, come sappiamo, un riporto proveniente da un registro CURRENT ADDRESS del DMAC 8237A, non modifica il corrispondente registro PAGE esterno; tutto ciò che possiamo fare quindi, consiste nel poter andare da X0000h a XFFFFh, sempre all'interno della pagina Xh. Proprio per questo motivo, il blocco più grande che possiamo trasferire con un DMAC 8237A è pari a 64 KiB; blocchi di dimensioni maggiori devono essere suddivisi in tanti sotto-blocchi, ciascuno dei quali non può superare i 64 KiB.

La programmazione dei primi 16 bit del registro di Figura 15.10 avviene come già illustrato in precedenza; successivamente, si scrivono i 4 bit del registro PAGE. Sui PC di classe XT è presente un unico DMAC 8237A; la Figura 15.11 elenca gli indirizzi di porta per i quattro registri PAGE a 4 bit. Sui primi PC di classe AT, la CPU di riferimento è la 80286, dotata di Address Bus a 24 linee, capace di indirizzare quindi sino a 224=16777216 byte di RAM (da 000000h a FFFFFFh); la situazione è perfettamente analoga a quanto è stato appena illustrato per la CPU 8086. Viene aggiunto un 8-bit latch esterno per ogni canale, in modo da avere registri CURRENT ADDRESS da 16+8=24 bit; la Figura 15.12 mostra la nuova situazione, con il registro dell'8-bit latch colorato in giallo. In questo caso, gli 8 bit più significativi di questo indirizzo fisico a 24 bit, rappresentano uno dei possibili 16777216/65536=256 blocchi da 64 KiB in cui risultano suddivisi i 16 MiB di RAM indirizzabili dalla CPU 80286.

Sui PC di classe AT sono presenti due DMAC 8237A; la Figura 15.13 elenca gli indirizzi di porta per gli otto registri PAGE a 8 bit. Come abbiamo visto nel tutorial Assembly Base, quando un PC di classe AT (o superiore) lavora in modalità reale, la linea A20 del suo Address Bus è disabilitata, per cui possiamo accedere solamente al primo MiB di RAM; in tal caso, solamente i 4 bit meno significativi dei registri PAGE sono importanti.

Con l'arrivo delle CPU con Address Bus a 32 (o più) linee, il DMAC 8237A non ha subito alcuna modifica; i registri PAGE continuano ad essere a 8 bit e, proprio per questo motivo, lo spazio di indirizzamento massimo resta limitato ai primi 16 MiB di RAM.

Il DMAC che svolge il ruolo di Slave effettua trasferimenti di dati a 8 bit; in questo caso, la gestione degli indirizzamenti è molto semplice. Per ogni BYTE trasferito, il contatore viene decrementato di 1, mentre il contenuto dei primi 16 bit del registro CURRENT ADDRESS viene incrementato [decrementato] di 1.

Il DMAC che svolge il ruolo di Master effettua trasferimenti di dati a 16 bit; come è stato spiegato in precedenza, in questo caso si ricorre ad un espediente che, per ogni WORD trasferita, permette di incrementare [decrementare] di 2 il contenuto dei primi 16 bit del registro CURRENT ADDRESS, mentre il contatore viene sempre decrementato di 1.
Innanzi tutto, l'indirizzo fisico da caricare nei 16+8=24 bit del registro CURRENT ADDRESS + PAGE deve essere prima diviso per due (shift di un bit verso destra); al momento di trasferire la prossima WORD, il DMAC legge l'indirizzo a 24 bit e lo moltiplica per due (shift di un bit verso sinistra). A questo punto, il vecchio contenuto a 24 bit del registro CURRENT ADDRESS + PAGE viene incrementato [decrementato] di 1, mentre il contatore viene decrementato di 1. E così via per le WORD successive.
Supponiamo, ad esempio, che i 24 bit dell'indirizzo fisico iniziale valgano 100 (in base 10); il valore da caricare nei 16+8=24 bit del registro CURRENT ADDRESS + PAGE sarà quindi: 100/2=50. Nel caso in cui il trasferimento dati preveda l'accesso in RAM nel verso crescente degli indirizzi, il DMAC legge il valore 50, lo moltiplica per due ottenendo 50*2=100, trasferisce la WORD presente a quell'indirizzo, incrementa 50 di 1 ottenendo 50+1=51 e infine decrementa il contatore di 1.
Successivamente, il DMAC legge il valore 51, lo moltiplica per due ottenendo 51*2=102, trasferisce la WORD presente a quell'indirizzo, incrementa 51 di 1 ottenendo 51+1=52 e infine decrementa il contatore di 1.
Proseguendo in questo modo si vede che, per ogni WORD trasferita, l'indirizzo viene incrementato di 2; analogamente, se il trasferimento dati prevedesse l'accesso in RAM nel verso decrescente degli indirizzi, per ogni WORD trasferita si otterrebbe un decremento di 2 dell'indirizzo. Il DMAC 8237A possiede quattro registri BASE ADDRESS, uno per ogni canale; la struttura di tali registri è del tutto identica a quella dei CURRENT ADDRESS mostrata in Figura 15.8.

I registri BASE ADDRESS sono accessibili solamente in scrittura attraverso la CPU, mentre l'accesso in lettura è interdetto; i loro indirizzi di porta sono identici a quelli dei registri CURRENT ADDRESS illustrati in Figura 15.9. Ciò significa che, l'indirizzo che scriviamo in un registro CURRENT ADDRESS in fase di programmazione, verrà simultaneamente scritto anche nel corrispondente registro BASE ADDRESS; tale indirizzo, viene utilizzato dal DMAC per riprogrammare il CURRENT ADDRESS in modalità Auto Init.
In modalità Auto Init, quando il contatore raggiunge lo zero (TC), la conseguente attivazione della linea EOP fa in modo che l'indirizzo memorizzato nel BASE ADDRESS venga copiato nel corrispondente CURRENT ADDRESS (e, come vedremo nel seguito, il valore memorizzato nel BASE WORD COUNT venga copiato nel corrispondente CURRENT WORD COUNT); a questo punto, parte in automatico il trasferimento di un nuovo blocco di dati. Il DMAC 8237A possiede quattro registri CURRENT WORD COUNT a 16 bit, uno per ogni canale; tali registri sono destinati a contenere il valore corrente del contatore che il DMAC utilizza per sapere quanti BYTE (o WORD) mancano per completare un trasferimento dati richiesto da una periferica connessa al corrispondente DMA channel. La struttura di un registro CURRENT WORD COUNT è illustrata in Figura 15.14. La programmazione dei registri CURRENT WORD COUNT deve essere effettuata quando il DMAC si trova in Program Condition; ciò accade, come sappiamo, nello stato SI del ciclo Idle o nello stato S0 del ciclo Active.
Supponendo di avere già resettato il Flip-Flop, i due accessi in scrittura che abbiamo effettuato in precedenza al registro CURRENT ADDRESS riposizionano il puntatore, sul BYTE basso di tutti i registri a 16 bit; a questo punto, possiamo procedere come al solito memorizzando nel CURRENT WORD COUNT, prima il BYTE basso (LSB) e poi il BYTE alto (MSB) del valore a 16 bit da cui partirà il conto alla rovescia. Gli indirizzi di porta dei registri CURRENT WORD COUNT sono illustrati in Figura 15.15. Il DMAC 8237A possiede quattro registri BASE WORD COUNT, uno per ogni canale; la struttura di tali registri è del tutto identica a quella dei CURRENT WORD COUNT mostrata in Figura 15.14.

I registri BASE WORD COUNT sono accessibili solamente in scrittura attraverso la CPU, mentre l'accesso in lettura è interdetto; i loro indirizzi di porta sono identici a quelli dei registri CURRENT WORD COUNT illustrati in Figura 15.15. Ciò significa che, il valore che scriviamo in un registro CURRENT WORD COUNT in fase di programmazione, verrà simultaneamente scritto anche nel corrispondente registro BASE WORD COUNT; tale valore, viene utilizzato dal DMAC per riprogrammare il CURRENT WORD COUNT in modalità Auto Init.
In modalità Auto Init, quando il contatore raggiunge lo zero (TC), la conseguente attivazione della linea EOP fa in modo che il valore memorizzato nel BASE WORD COUNT venga copiato nel corrispondente CURRENT WORD COUNT (e, come abbiamo visto prima, l'indirizzo memorizzato nel BASE ADDRESS venga copiato nel corrispondente CURRENT ADDRESS); a questo punto, parte in automatico il trasferimento di un nuovo blocco di dati. Il DMAC 8237A possiede un registro COMMAND da 8 bit; lo scopo di tale registro è il controllo delle operazioni svolte dal dispositivo.

La programmazione del registro COMMAND deve essere effettuata quando il DMAC si trova in Program Condition; ciò accade, come sappiamo, nello stato SI del ciclo Idle o nello stato S0 del ciclo Active.
Il registro COMMAND può essere resettato inviando un segnale RESET o attraverso un comando Master Clear.

La Figura 15.16 mostra la struttura del registro COMMAND. Se il bit 0 vale 0 (trasferimento dati Memory to Memory disabilitato), il bit 1 è indefinito.
Se il bit 0 vale 1 (trasferimento dati Memory to Memory abilitato), il bit 3 è indefinito.
Se il bit 3 vale 1 (Compressed Timing), il bit 5 è indefinito.

Il registro COMMAND viene inizializzato a 00000000b da un segnale RESET o dal comando Master Clear; generalmente, conviene lasciare le impostazioni predefinite, a meno che non si abbia la necessità del Compressed Timing o della Rotating Priority.

Gli indirizzi di porta del registro COMMAND sono illustrati in Figura 15.17. Il DMAC 8237A possiede quattro registri MODE da 6 bit, uno per ogni canale; lo scopo di tali registri è impostare tipi e modalità di trasferimento dati.

La programmazione dei registri MODE deve essere effettuata quando il DMAC si trova in Program Condition; ciò accade, come sappiamo, nello stato SI del ciclo Idle o nello stato S0 del ciclo Active.

La Figura 15.18 mostra la struttura dei registri MODE. Per i bit 2 e 3 (Transfer Type), la coppia 11 non è permessa; inoltre, se i bit 6 e 7 (Transfer Mode) valgono 11 (Cascade Mode), allora i bit 2 e 3 sono indeterminati.

Gli indirizzi di porta dei registri MODE sono illustrati in Figura 15.19. Come si può notare, ogni DMAC 8237A ha una sola porta hardware per tutti i quattro registri MODE; infatti, si utilizzano i primi due bit per specificare a quale dei quattro registri ci si sta riferendo. In sostanza, degli 8 bit totali, solo gli ultimi 6 (dal 2 al 7) vengono usati per le impostazioni; grazie a questo espediente quindi, è come se avessimo quattro registri da 6 bit ciascuno. Il DMAC 8237A possiede un registro REQUEST da 4 bit; lo scopo di tale registro è attivare o disattivare una DMA Request via software, anziché via hardware. Questa funzionalità diventa molto utile quando chi richiede un accesso diretto alla memoria non è abilitato ad inviare una DREQ hardware; ad esempio, abbiamo visto che nel caso di un trasferimento dati di tipo Memory to Memory, la RAM deve usare i canali 0 e 1, ma non essendo fisicamente connessa ad alcun canale del DMAC, è compito del programmatore avviare le operazioni inviando una DREQ di tipo software al channel 0.

La programmazione del registro REQUEST deve essere effettuata quando il DMAC si trova in Program Condition; ciò accade, come sappiamo, nello stato SI del ciclo Idle o nello stato S0 del ciclo Active.

La Figura 15.20 mostra la struttura del registro REQUEST. Questo registro è virtualmente a 4 bit in quanto, i bit 0 e 1 selezionano il canale e, per ciascun canale, il bit 2 permette di attivare o disattivare la DMA Request via software; il contenuto dei bit da 3 a 7 viene ignorato.

Una DMA Request via software non è mascherabile, ma è soggetta all'ordine di priorità stabilito dal DMAC; inoltre, il canale da utilizzare deve trovarsi in Block Transfer Mode.
Al termine del trasferimento dati avviato tramite una DMA Request via software, il segnale TC riporta a 0 il bit del canale selezionato; lo stesso risultato viene ottenuto inviando un EOP o ponendo direttamente a 0 tale bit sempre attraverso una DMA Request via software.

Gli indirizzi di porta del registro REQUEST sono illustrati in Figura 15.21. Il DMAC 8237A possiede un registro MASK da 4 bit; lo scopo di tale registro è attivare o disattivare il mascheramento dei canali via software.

La programmazione del registro MASK deve essere effettuata quando il DMAC si trova in Program Condition; ciò accade, come sappiamo, nello stato SI del ciclo Idle o nello stato S0 del ciclo Active.

La Figura 15.22 mostra la struttura del registro MASK. Questo registro è virtualmente a 4 bit in quanto, i bit 0 e 1 selezionano il canale e, per ciascun canale, il bit 2 permette di attivare o disattivare il mascheramento via software; il contenuto dei bit da 3 a 7 viene ignorato.

Un canale mascherato non risponde ad una eventuale DREQ che arriva da una periferica; come è stato spiegato in precedenza, il mascheramento non ha invece effetto se la DMA Request arriva via software.
Normalmente, questi aspetti vengono gestiti dal DMAC via hardware; al termine di un trasferimento dati, il conseguente segnale EOP fa in modo che il canale utilizzato venga automaticamente mascherato, a meno che non si trovi in modalità Auto Init. Il segnale RESET pone a 1 i Bit Mask di tutti i quattro canali; di conseguenza, le DMA Request che arrivano dalle periferiche non vengono prese in carico, finché il bit del corrispondente canale non viene riportato a 0 attraverso il registro MASK o attraverso il comando Clear Mask Register (che pone a 0 tutti i quattro Bit Mask).

Gli indirizzi di porta del registro MASK sono illustrati in Figura 15.23. Il DMAC 8237A possiede un registro STATUS da 8 bit; lo scopo di tale registro è fornire informazioni sui trasferimenti di dati completati (TC) e sulle DMA Requests in attesa di elaborazione.

La Figura 15.24 mostra la struttura del registro STATUS. Ogni volta che il contatore di un canale raggiunge lo 0 (TC), viene attivato il segnale EOP e viene posto a 1 il corrispondente bit (tra 0 e 3) del registro STATUS; tali bit vengono riportati a 0 da un comando RESET o da una operazione di lettura del registro stesso.
Ogni volta che una periferica effettua una DMA Request, viene posto a 1 il corrispondente bit (tra 4 e 7) del registro STATUS.

Gli indirizzi di porta del registro STATUS sono illustrati in Figura 15.25. Come si può notare, gli indirizzi di porta del registro STATUS sono gli stessi del registro COMMAND; tali due registri si distinguono però per il fatto che il primo è accessibile in sola lettura e il secondo in sola scrittura. Il DMAC 8237A possiede un registro TEMPORARY da 8 bit; lo scopo di tale registro è memorizzare temporaneamente un BYTE durante un trasferimento dati Memory to Memory; ogni BYTE viene letto dall'indirizzo sorgente, trasferito nel registro TEMPORARY e da qui scritto all'indirizzo destinazione.
Il registro TEMPORARY è accessibile in sola lettura dalla CPU quando il DMAC si trova in Program Condition.

Gli indirizzi di porta del registro TEMPORARY sono illustrati in Figura 15.26.

15.2.8 Comandi software del DMAC 8237A

Il DMAC 8237A accetta tre comandi software che possono essere inviati quando il dispositivo è in Program Condition; analizziamoli in dettaglio. Questo comando deve essere eseguito prima di qualsiasi operazione di accesso in lettura o scrittura ai vari registri ADDRESS e WORD COUNT a 16 bit; l'esecuzione del comando fa in modo che il Data Bus interno a 8 bit punti al BYTE basso (LSB) di tutti i registri a 16 bit, permettendo così l'accesso in sequenza, prima al LSB e poi al MSB. Ogni accesso modifica lo stato del Flip-Flop, in modo che il puntatore ai registri si sposti in modo ciclico secondo la sequenza LSB, MSB, LSB, MSB e così via.
Essendo presente un unico Flip-Flop, dobbiamo seguire una sequenza precisa per riempire i due registri CURRENT ADDRESS e CURRENT WORD COUNT; dopo il reset del Flip-Flop, la sequenza è: LSB + MSB del CURRENT ADDRESS, seguiti da LSB + MSB del CURRENT WORD COUNT.

Gli indirizzi di porta per il comando Clear First/Last Flip-Flop sono illustrati in Figura 15.27. In teoria, si può scrivere un qualunque valore a 8 bit nelle porte di Figura 15.27; in genere, però, si scrive il valore 0. Questo comando ha lo stesso effetto dell'invio al DMAC 8237A di un segnale RESET via hardware. I registri COMMAND, STATUS, REQUEST e TEMPORARY vengono azzerati, il Flip-Flop viene resettato e i bit MASK (bit 2) di tutti i quattro canali vengono posti a 1 (mascheramento attivo); infine, il DMAC 8237A passa nel ciclo Idle.

Gli indirizzi di porta per il comando Master Clear sono illustrati in Figura 15.28. Come si può notare, gli indirizzi di porta del comando Master Clear sono gli stessi del registro TEMPORARY; tali due registri si distinguono però per il fatto che il primo è accessibile in sola scrittura e il secondo in sola lettura.

In teoria, si può scrivere un qualunque valore a 8 bit nelle porte di Figura 15.28; in genere, però, si scrive il valore 0. Questo comando pone a 0 i bit MASK (bit 2) di tutti i quattro canali, in modo che essi siano disponibili per eventuali DMA Requests; in sostanza, ogni canale è unmasked.

Gli indirizzi di porta per il comando Clear Mask Register sono illustrati in Figura 15.29. In teoria, si può scrivere un qualunque valore a 8 bit nelle porte di Figura 15.29; in genere, però, si scrive il valore 0.

15.3 Programmazione del DMAC 8237A e delle periferiche abilitate al DMA

Le informazioni esposte sino a questo punto sono più che sufficienti per capire come si debba procedere per programmare il DMAC 8237A in modo che risponda ad una DMA Request inviata da una periferica; in generale, i passi da svolgere sono i seguenti: Generalmente, non appena si finisce di programmare la periferica, il trasferimento dati parte in automatico; infatti, la periferica stessa provvede ad inviare subito una DREQ hardware attraverso il canale che la collega al DMAC. L'eccezione è rappresentata da quelle periferiche, come la RAM, non predisposte per l'invio di una DREQ hardware; in tal caso, abbiamo visto che è compito del programmatore inviare una DREQ software tramite il registro REQUEST.

15.3.1 Predisposizione del buffer di memoria

In relazione all'indirizzo iniziale del buffer di memoria che utilizziamo per il trasferimento dati, esiste un aspetto importantissimo che riguarda l'allineamento richiesto dal DMAC; come è stato spiegato in precedenza, se abbiamo a disposizione un indirizzo fisico iniziale allineato alla pagina, del tipo X0000h, possiamo muoverci da X0000h a XFFFFh (o viceversa) senza rischiare il wrap around.
Il problema che si presenta è che i SO, generalmente, forniscono blocchi di memoria con allineamento diverso da quello alla pagina; ad esempio, sappiamo che il DOS restituisce sempre blocchi di memoria con indirizzo logico iniziale SEG:OFFSET del tipo XXXXh:0000h, allineato al paragrafo. Nel corso Assembly Base abbiamo visto che un indirizzo logico si converte in modo molto semplice in un indirizzo fisico calcolando:
(SEG * 16) + OFFSET
La moltiplicazione per 16 equivale ad uno shift a sinistra di 4 bit del valore contenuto in SEG; la successiva somma in questo caso è inutile in quanto OFFSET vale sempre 0000h.
Supponiamo che l'indirizzo logico iniziale sia 3F40h:0000h; otteniamo allora l'indirizzo fisico:
(3F40h * 10h) + 0000h = 3F400h
L'indirizzo fisico 3F400h può essere visto come l'offset F400h all'interno della pagina di memoria n. 3h; come si può notare, supponendo che il DMAC segua l'ordine crescente degli indirizzi, in questo caso possiamo andare solamente da 3F400h a 3FFFFh, per un totale di soli BFFh byte (3071 byte)!
Per aggirare questo problema, si può decidere di allocare un blocco da 128 KiB di memoria (il doppio di 64 KiB); a questo punto, se l'indirizzo logico iniziale è, come prima, 3F40h:0000h, ricaviamo l'indirizzo fisico 3F400h. Sommiamo ora 1 alla pagina ottenendo 3h+1h=4h e imponiamo l'offset iniziale 0000h; alla fine, otteniamo l'indirizzo 40000h, allineato alla pagina, da cui parte un blocco da 64 KiB sicuramente contenuto all'interno del blocco da 128 KiB allocato in precedenza.
In pratica, stiamo facendo avanzare 3F400h di BFFh+1h, in modo da ottenere:
3F400h + (BFFh + 1h) = 3F400h + C00h = 40000h


Per quanto riguarda la gestione del blocco di memoria RAM coinvolto nel trasferimento dati, si possono presentare diverse situazioni; ovviamente, il DMAC 8237A fornisce tutte le funzionalità necessarie per affrontare i vari casi.
Uno dei casi possibili consiste in una grossa area della RAM (superiore a 64 KiB), il cui contenuto deve essere trasferito ad una periferica; ciò accade, ad esempio, quando vogliamo scrivere su disco tale area attraverso il controller dell'hard disk. In una situazione del genere, dobbiamo suddividere l'area di memoria in tanti sotto-blocchi, ciascuno dei quali non può superare i 64 KiB; dopo aver programmato il DMAC e la periferica per il primo sotto-blocco, possiamo avviare il trasferimento dati. Terminato il trasferimento del primo sotto-blocco, dobbiamo riprogrammare il DMAC e la periferica in modo che trasferiscano il sotto-blocco successivo; e così via sino all'ultimo sotto-blocco. Appare evidente che, in questo caso, il DMAC 8237A e la periferica devono operare in Single Cycle; non è possibile ricorrere all'Auto Init in quanto l'indirizzo iniziale varia da un sotto-blocco all'altro.
Un altro caso possibile consiste in un trasferimento dati opposto a quello appena descritto; ciò accade, ad esempio, quando vogliamo leggere un file audio dall'hard disk e trasferirlo ad una scheda audio. In una situazione del genere, possiamo anche ricorrere ad un buffer (da 64 KiB max.) nella RAM. Il file audio viene innanzi tutto suddiviso in tanti sotto-blocchi e il primo di essi viene caricato nel buffer di memoria; dopo aver programmato il DMAC e la periferica per il primo sotto-blocco, possiamo avviare il trasferimento dati. Terminato il trasferimento del primo sotto-blocco, dobbiamo caricare nel buffer il secondo sotto-blocco e riprogrammare il DMAC e la periferica per un nuovo trasferimento dati; e così via sino all'ultimo sotto-blocco. Appare evidente che, in questo caso, il DMAC e la periferica possono operare in Single Cycle, ma anche in Auto Init, visto che l'indirizzo iniziale del buffer è sempre lo stesso; in questa seconda eventualità, bisogna ricordare che anche la dimensione del blocco da trasferire deve essere sempre la stessa.

Nel precedente capitolo è stato spiegato che, per gestire la modalità Auto Init, si utilizza spesso il metodo del double-buffering illustrato in Figura 15.30. Il DMAC 8237A viene programmato per un trasferimento da 64 KiB di dati, mentre la periferica per 32 KiB (la metà esatta). Dopo il trasferimento della prima metà del buffer, la periferica genera una IRQ e la ISR che intercetta la relativa interruzione carica altri 32 KiB di dati nella stessa prima metà; nel frattempo, il DMAC sta trasferendo i 32 KiB di dati presenti nella seconda metà del buffer. Dopo il trasferimento della seconda metà del buffer, la periferica genera una seconda IRQ e la ISR che intercetta la relativa interruzione carica altri 32 KiB di dati nella stessa seconda metà; nel frattempo, il DMAC si è "auto-inizializzato" e sta trasferendo i 32 KiB di nuovi dati presenti nella prima metà del buffer. E così via sino alla fine del trasferimento dati.
Generalmente, l'ultimo blocco da trasferire ha una dimensione inferiore a 64 KiB, per cui non può essere trasferito in Auto Init; in questo caso, il DMAC e la periferica devono essere riprogrammati in modo che per l'ultimo blocco passino in Single Cycle.

15.3.2 Installazione della ISR

Nel caso generale, al termine del trasferimento di ogni blocco di dati, la periferica che ha inviato una DMA Request provvede a generare una IRQ; a questo punto, intercettando la relativa INT tramite un'apposita ISR, possiamo decidere cosa fare. Se il trasferimento dati è completo, possiamo uscire dal programma; se, invece, ci sono altri blocchi di dati da trasferire, dobbiamo riempire il buffer con il blocco successivo e riprogrammare il DMAC e la periferica per un nuovo trasferimento. Per sapere quale INT è associata ad una determinata IRQ, basta fare riferimento alle Figure 3.10 e 3.11 del Capitolo 3 (Il PIC 8259 - Programmable Interrupt Controller). In sostanza, se la periferica genera una IRQ compresa tra 0 e 7 (PIC Master), si ottiene la relativa INT sommando 8 alla IRQ stessa; se la periferica genera una IRQ compresa tra 8 e 15 (PIC Slave), si ottiene la relativa INT sommando 104 alla IRQ stessa.

Non bisogna dimenticare che la nostra ISR è associata in questo caso ad una IRQ hardware; prima di terminare quindi, tale ISR deve inviare un EOI (End Of Interrupt) al PIC. Come sappiamo, se la IRQ è associata al PIC Master, si invia un EOI allo stesso PIC Master; se la IRQ è associata al PIC Slave, si invia un EOI, prima allo stesso PIC Slave e poi al PIC Master.
Anche certe periferiche potrebbero richiedere una sorta di EOI per sapere quando la ISR ha terminato il proprio lavoro; in un caso del genere, il programmatore è tenuto ad inserire nella ISR stessa tutto il codice necessario. Per la SB16, ad esempio, abbiamo visto che la ISR deve inviare al DSP un segnale ACK prima di terminare; tutti i dettagli su tale aspetto sono stati illustrati nella sezione 14.5.4 del Capitolo 14.

15.3.3 Programmazione del DMAC 8237A

In riferimento alla programmazione del DMAC, possiamo elencare i seguenti passi necessari per la corretta inizializzazione del dispositivo: In casi particolari, si può avere anche la necessità di modificare alcuni bit del registro COMMAND (ad esempio, trasferimento dati Memory to Memory, Compressed Timing, Rotating Priority); si tenga presente, però, che il computer (o la macchina virtuale) che si sta utilizzando, potrebbe anche non supportare alcune delle funzionalità fornite dal DMAC attraverso tale registro. Per illustrare in pratica questi aspetti, supponiamo di dover lavorare sul canale 1 a 8 bit (DMAC Slave); prima di tutto, definiamo una serie di costanti simboliche contenenti gli indirizzi di porta dei vari registri: Richiediamo ora un blocco di memoria (da usare come buffer) al DOS il quale, come sappiamo, ci restituisce un valore a 16 bit (XXXXh) che rappresenta la componente SEG di un indirizzo logico SEG:OFFSET del tipo XXXXh:0000h (allineato al paragrafo); tale indirizzo logico deve essere convertito (con il metodo che già conosciamo) in un indirizzo fisico da caricare nei registri CURRENT ADDRESS e PAGE del channel 1.
Decidiamo di far lavorare il DMAC 8237A in modo Single Transfer, tipo Read (lettura dalla RAM e scrittura in una periferica), incremento degli indirizzi e Auto Init disabilitato (Single Cycle); nel registro MODE dobbiamo caricare quindi 01001001b. Se la componente SEG del buffer di memoria e il contatore sono contenuti nelle due variabili segBuffer e blockSize, possiamo scrivere allora il seguente codice:

15.3.4 Programmazione della periferica

Terminata la programmazione del DMAC 8237A, bisogna svolgere lo stesso lavoro sulla periferica che intende effettuare una DMA Request; appare ovvio che la periferica stessa debba essere programmata in modo coerente con quanto fatto con il DMAC. Se il DMAC è in Single Cycle a 8 bit, anche la periferica deve essere in Single Cycle a 8 bit; analogamente, se il DMAC è in Auto Init a 16 bit, anche la periferica deve essere in Auto Init a 16 bit.

Per illustrare un esempio pratico, facciamo riferimento alla programmazione della SB16 in DMA Mode per l'esecuzione (output) di un file audio; tale scheda audio è stata analizzata in dettaglio nel precedente capitolo. I comandi della SB16 che ci interessano sono quelli indicati con 41h, Bxh e Cxh. Dopo aver ricevuto questo comando, il DSP resta in attesa di un dato da 1 word che rappresenta la frequenza di campionamento del flusso audio in uscita; tale dato deve essere inviato in due parti: prima il byte alto e poi il byte basso.
I valori validi per la frequenza di campionamento spaziano da 5000 Hz a 45000 Hz; a differenza di quanto accade per la costante di tempo, la frequenza di campionamento non deve essere moltiplicata per 2 nel caso di audio stereo. Questo comando permette di programmare il DSP per un trasferimento dati a 16 bit tramite il DMAC Master; i 4 bit più significativi del comando valgono Bh (1011b), mentre gli altri 4 possono variare assumendo il significato illustrato in Figura 15.31. D3=0 abilita il DAC (output), mentre D3=1 abilita l'ADC (input)
D2=0 abilita il Single Cycle DMA Mode, mentre D2=1 abilita l'Auto-Init DMA Mode.
D1=0 disabilita la coda FIFO, mentre D1=1 la abilita

Dopo aver ricevuto questo comando, il DSP resta in attesa di due parametri; il primo, da 1 byte, assume il significato illustrato in Figura 15.32. D5=0 imposta la modalità mono, mentre D5=1 imposta la modalità stereo.
D4=0 indica che i campioni sono numeri a 16 bit senza segno, mentre D4=1 indica che i campioni sono numeri a 16 bit con segno.

Il secondo parametro, da 1 word, indica il numero di campioni audio meno 1 da trasferire; tale parametro deve essere inviato in due parti: prima il byte basso e poi il byte alto. Questo comando permette di programmare il DSP per un trasferimento dati a 8 bit tramite il DMAC Slave; i 4 bit più significativi del comando valgono Ch (1100b), mentre gli altri 4 sono organizzati esattamente come in Figura 15.31. I due parametri richiesti da questo comando hanno lo stesso significato di quelli del comando Bxh (Figura 15.32), con il bit D4 che, stavolta, è riferito a campioni audio a 8 bit.


Come abbiamo visto nel precedente capitolo, i comandi resi disponibili dalla SB16 devono essere inviati alla porta BASE+0Ch (DSP Write Command/Data), dove BASE rappresenta l'indirizzo base della porta di I/O principale della scheda; inoltre, sappiamo che prima di scrivere in tale porta, dobbiamo attendere che il bit 7, allo stesso indirizzo BASE+0Ch (DSP Write-Buffer Status) si sia portato a 0. Nell'esempio che segue, supponiamo di far svolgere questo lavoro ad una procedura denominata sb16_waitforwrite; dopo la chiamata, tale procedura ci restituisce DX=BASE+0Ch, per cui abbiamo già l'indirizzo che ci serve nello stesso DX.
Partiamo ora dall'esempio illustrato prima per il DMAC 8237A (trasferimento dati in Single Cycle a 8 bit, con lettura dalla RAM e scrittura in una periferica; supponiamo che tale periferica sia proprio la SB16, che vogliamo utilizzare per eseguire un file audio con campioni stereo a 8 bit (unsigned).
La sequenza di programmazione richiesta dalla SB16 è la seguente: Se la frequenza di campionamento e il numero di campioni da eseguire si trovano nelle due variabili sampleRate e blockSize, possiamo scrivere quindi il seguente codice: Come è stato spiegato in precedenza, il trasferimento dati parte in automatico appena eseguite queste istruzioni; quando tutti i blockSize byte sono stati trasferiti, la SB16 genera una IRQ.

Se vogliamo operare in modalità Auto Init, dobbiamo apportare piccole modifiche al codice appena illustrato. In relazione al DMAC 8237A, il valore da inviare al registro MODE diventa 01011001b; in relazione alla SB16, il comando 0C0h deve essere sostituito con 0C6h.
Ovviamente, in questo caso dobbiamo anche provvedere a predisporre il Double Buffer e a gestire correttamente la fine del trasferimento dati riprogrammando il DMAC e la SB16 in modalità Single Cycle per l'ultimo blocco.

15.4 Esempi pratici

Nel precedente capitolo è stata illustrata la struttura dei file audio di tipo wavetable (WAV); l'esempio presentato in questo capitolo consiste proprio nell'esecuzione di un file WAV a 8 o 16 bit, mono o stereo, in DMA Mode, attraverso la SB16 e il DMAC 8237A.
Ovviamente, il programma ha uno scopo puramente didattico per cui, per non rendere il codice troppo complicato, faremo riferimento a semplici file WAV generati dal software sox; si tratta di file costituiti da un blocco iniziale di informazioni, immediatamente seguito dal blocco dei dati audio in formato PCM non compresso (vedere il capitolo precedente per i dettagli).

15.4.1 Esecuzione di un file WAV in DMA Mode Single Cycle

L'esempio presentato nel seguito si serve della libreria SB16LIB, utilizzata anche nel precedente capitolo; si consiglia di effettuare nuovamente il download del file sb16libexe.zip in modo da avere la versione più aggiornata.

Per le procedure destinate alla gestione del DMAC 8237A ci serviamo di una ulteriore libreria.
Nella sezione Librerie di supporto per il corso assembly dell’ Area Downloads di questo sito, è presente una libreria, denominata DMACLIB, che può essere linkata ai programmi destinati alla generazione di eseguibili in formato EXE; all'interno del pacchetto dmaclibexe.zip è presente la libreria vera e propria DMACLIB.ASM, l'include file DMACLIBN.INC per il NASM e l'include file DMACLIBM.INC per il MASM.
Per la creazione dell'object file di DMACLIB.ASM è necessario il NASM; il comando da impartire è:
nasm -f obj dmaclib.asm
L'object file così ottenuto è perfettamente linkabile anche ai programmi scritti con MASM.

La libreria DMACLIB contiene le due uniche procedure pubbliche dmac_command e dmac_register, di tipo FAR, destinate alla gestione dei DMAC Master e Slave; la prima procedura permette di inviare comandi, mentre la seconda si occupa della programmazione dei vari registri.
La procedura dmac_command richiede il codice del comando in AH e il comando da inviare in AL; per il codice del comando vengono utilizzate varie costanti simboliche definite nell'include file. Ad esempio, per inviare il comando Clear First/Last Flip-Flop al DMAC Master, dobbiamo caricare in AL il valore 0 e in AH il valore:
DMACMASTER OR CLEARFLIPFLOP
La procedura dmac_register richiede il codice del registro in AH e il valore da scrivere (o leggere) in AL; per il codice del registro vengono utilizzate varie costanti simboliche definite nell'include file. Ad esempio, per scrivere il valore 01001001b nel registro MODE del DMAC Slave, dobbiamo caricare in AL il valore 01001001b e in AH il valore:
DMACSLAVE OR MODE
In ogni caso, il codice sorgente della libreria DMACLIB è ben commentato e risulta abbastanza semplice da capire.

La Figura 15.33 illustra il listato del programma DMACTEST che usa le librerie SB16LIB e DMACLIB per suonare un file WAV (a 8 o 16 bit, mono o stereo) attraverso il DSP della SB16 in DMA Mode Single Cycle. Come si può notare, la parte iniziale del programma si occupa del controllo degli errori ed è del tutto simile a quella dell'esempio presentato nel precedente capitolo; il nucleo del programma anche in questo caso è formato dal loop principale (main_music_loop) e dalla ISR (new_intsb16).

Innanzi tutto, visto che il file WAV viene caricato (un blocco alla volta) in un buffer di memoria, si provvede a restituire al DOS la RAM in eccesso occupata dall'eseguibile DMACTEST.EXE (vedere il Capitolo 6 sulla memoria base del computer).
Accedendo all'Environment Segment si verifica se è presente la stringa di ambiente BLASTER; in caso affermativo si procede all'estrazione dei vari parametri (è richiesto solo il base address).
Si procede poi al reset del DSP e si verifica se la sua versione è 4.xx; come sappiamo, questo passo è necessario per sapere se abbiamo a che fare con una SB16. Subito dopo, viene effettuato il reset anche del MIXER e viene impostato al massimo il Master Volume dei canali destro e sinistro; il volume di ogni canale è un valore a 5 bit (Figura 14.29 del Capitolo 14) e quindi può variare tra 0 e 25-1=31.

Il programma si aspetta che gli venga passato un file WAV come parametro dalla linea di comando; ad esempio:
dmactest sound1.wav
Il file viene aperto e si procede così alla lettura della sua parte iniziale, che dovrebbe contenere i blocchi header, fmt e data; se tali blocchi non vengono trovati, il programma termina in quanto il formato del file non è valido.
Se si tratta di un file WAV valido, vengono estratte tutte le informazioni necessarie per conoscere il formato dei campioni audio memorizzati; come è stato già spiegato, il programma si aspetta di trovare un flusso audio in formato PCM non compresso, a 8 o 16 bit, mono o stereo.
Se tali requisiti risultano verificati, abbiamo a che fare con quattro possibilità: In base al tipo di formato, procediamo alla conseguente inizializzazione dei vari parametri destinati alla SB16 e al DMAC; è necessario ricordare che i trasferimenti di dati a 8 bit vengono gestiti dal DMAC Slave, mentre quelli a 16 bit sono di competenza del DMAC Master.
Terminata questa fase, si passa all'allocazione del buffer di memoria, con la conversione dell'indirizzo logico allineato al paragrafo, in un indirizzo fisico allineato alla pagina; in tale buffer viene caricato il primo blocco di dati audio letto dal file WAV.
Il passo finale consiste nell'installazione della ISR (che risponde alle IRQ della SB16) e nella programmazione del DMAC e della SB16 con i parametri inizializzati in precedenza; come già sappiamo, a questo punto parte in automatico il trasferimento dati.
La ISR, ad ogni chiamata, decrementa il contatore dei blocchi da eseguire (numBlocks), carica un nuovo blocco nel buffer e riprogramma il DMAC e la SB16; la stessa ISR, prima di terminare, invia un ACK alla SB16 e un EOI al PIC Master (o ai PIC Slave e Master se si sta usando una IRQ gestita dal PIC Slave).
Il loop principale main_music_loop termina il programma quando il numero di blocchi da eseguire raggiunge lo 0; viene gestita anche l'uscita anticipata dal loop se l'utente preme un tasto qualunque. Per uscire anticipatamente dal loop viene inviato alla SB16 il comando Pause DMA Mode digitized sound I/O, che vale D0h per il trasferimento dati a 8 bit e D5h per quello a 16 bit; una volta ricevuto questo comando, la SB16 disabilita subito la sua linea DREQ permettendo così al DMAC di restituire alla CPU il controllo dei bus di sistema.
Come si può notare nel listato, questa situazione viene gestita tramite la variabile exit_flag, che viene posta a 1 dal loop principale quando l'utente preme un tasto qualunque; la ISR vede che exit_flag vale 1 e invia subito il comando Pause DMA Mode digitized sound I/O alla SB16 la quale, in questo caso particolare, non ha bisogno di ricevere un ACK.

A differenza dell'esempio presentato nel precedente capitolo, nel programma di Figura 15.33 il loop principale e la ISR non sono due aree critiche; ciò è dovuto al fatto che il DMAC e la SB16, una volta programmati, effettuano il trasferimento dati autonomamente, senza impegnare la CPU. Inoltre, tramite il registro MODE viene impostata la modalità Single Transfer la quale, come sappiamo, fa in modo che il DMAC si alterni con la CPU nella gestione dei bus di sistema; in questo modo, la CPU stessa può svolgere altri compiti mentre il DMAC è impegnato nel trasferimento dati.

La Figura 15.34 mostra il programma DMACTEST.EXE in esecuzione sull'emulatore DOSBox.

15.4.2 Esecuzione di un file WAV in DMA Mode Auto Init

Il programma di Figura 15.33 utilizza un buffer di memoria di dimensione prestabilita e con indirizzo iniziale fisso, per cui appare evidente l'inutilità di dover riprogrammare il DMAC e la SB16 alla fine del trasferimento di ogni blocco di dati; si tratta chiaramente di una situazione ideale per l'impiego della modalità Auto Init. Grazie all'Auto Init, il DMAC e la SB16 vengono programmati solo la prima volta, mentre la ISR si limita a caricare ad ogni chiamata un nuovo blocco di dati; inoltre, l'uso della tecnica del double buffering permette di aumentare enormemente l'efficienza del programma eliminando i tempi morti. Il programma di Figura 15.33, ad esempio, sui PC più lenti produce un rumore ad intervalli regolari di tempo durante l'esecuzione di un file audio; ciò è dovuto proprio al fatto che, al termine del trasferimento di ogni blocco di dati, si perde un tempo relativamente lungo per riprogrammare il DMAC e la SB16.

Le modifiche da apportare al file DMACTEST.ASM per la conversione alla modalità Auto Init sono relativamente semplici, anche perché, le due librerie SB16LIB e DMACLIB sono già predisposte per svolgere il lavoro più complesso; l'unico aspetto impegnativo riguarda l'implementazione del double buffering.
Innanzi tutto, osserviamo che il registro MODE del DMAC abilita l'Auto Init quando il suo bit in posizione 4 vale 1; la SB16, a sua volta, lavora in Auto Init quando riceve il comando C6h per l'output a 8 bit e B6h per l'output a 16 bit. Nella fase di inizializzazione del registro MODE del DMAC usiamo quindi il valore 01011000b (Read, Auto Init, Address Increment, Single Transfer) a cui si deve aggiungere poi il canale DMA; per la SB16 sostituiamo le due costanti OUT8BITSC e OUT16BITSC con OUT8BITAI e OUT16BITAI (vedere l'include file di SB16LIB).
Nelle due procedure program_dmac e program_sb16, le costanti appena illustrate vengono sottoposte ad un AND logico con apposite maschere di bit; ciò è necessario per riprogrammare il DMAC e la SB16 in Single Cycle al momento di trasferire l'ultimo blocco di dati. Il cambio di modalità viene svolto dalla ISR quando il decremento della variabile numBlocks (numero di blocchi restanti) produce il valore 1.

Per la gestione del double buffering utilizziamo una tecnica basata sull'uso di un buffer la cui dimensione in byte è un valore esprimibile come potenza intera di 2; ad esempio 32768, 16384 e così via. L'offset iniziale nel buffer è ovviamente 0000h; la procedura read_nextblock, ad ogni chiamata, carica un nuovo blocco di dati nel buffer e dopo incrementa l'offset sommandogli una quantità predefinita pari alla metà delle dimensioni del buffer stesso. Supponiamo che il buffer abbia una dimensione pari a 32768 byte (8000h byte); l'incremento dell'offset sarà quindi 32768/2=16384 byte (4000h byte) alla volta. Il buffer risulta allora suddiviso in due parti, per cui, ogni 32768 byte di dati trasferiti dal DMAC, la SB16 genera due IRQ.
Il nostro obiettivo è fare in modo che l'offset riparta da 0000h ogni 2 incrementi; ciò si ottiene attraverso un AND logico tra l'offset stesso e FBLOCK_SIZE-1, dove FBLOCK_SIZE è la dimensione del buffer. Ad esempio, supponiamo che FBLOCK_SIZE=32768 byte (8000h byte), con incremento dell'offset pari quindi a 16384 byte (4000h byte) alla volta; considerando che FBLOCK_SIZE-1=32767, pari a 7FFFh, dopo il primo incremento di 16384 byte abbiamo:
(0000h + 4000h) AND 7FFFh = 4000h AND 7FFFh = 4000h
Dopo il secondo incremento di 16384 byte abbiamo:
(4000h + 4000h) AND 7FFFh = 8000h AND 7FFFh = 0000h
Quindi, l'offset si azzera ogni due incrementi!
Come spiegato in precedenza, questa tecnica funziona quando la dimensione del buffer è esprimibile come potenza intera di 2; ovviamente, l'incremento dell'offset, a sua volta, deve essere la metà esatta della dimensione del buffer.

La Figura 15.35 illustra il listato del programma DMACTST1 che usa le librerie SB16LIB e DMACLIB per suonare un file WAV (a 8 o 16 bit, mono o stereo) attraverso il DSP della SB16 in DMA Mode Auto Init. La Figura 15.36 mostra il programma DMACTST1.EXE in esecuzione sull'emulatore DOSBox.

15.5 Creazione di file WAV in formato PCM a 8 o 16 bit, mono o stereo

In analogia a quanto spiegato nel precedente capitolo, anche i file WAV possono essere creati facilmente con il programma gratuito sox.
Se abbiamo a disposizione un file audio sorgente in un formato supportato da sox, possiamo procedere subito alla sua conversione; ad esempio, partendo da un file sound1.ogg (formato audio Ogg Vorbis), con campioni almeno a 16 bit stereo, possiamo ottenere un WAV in formato PCM a 8 bit mono con il comando:
sox sound1.ogg -S -c 1 -b 8 sound8m.wav
Se vogliamo il formato PCM a 8 bit stereo:
sox sound1.ogg -S -c 2 -b 8 sound8s.wav
Se vogliamo il formato PCM a 16 bit mono:
sox sound1.ogg -S -c 1 -b 16 sound16m.wav
Se vogliamo il formato PCM a 16 bit stereo:
sox sound1.ogg -S -c 2 -b 16 sound16s.wav
Il parametro -c specifica il numero di canali, il parametro -b specifica il numero di bit per campione, mentre il parametro -S (show progress) permette di vedere il lavoro di conversione che SOX sta effettuando.
Bisogna ricordare che la SB16 accetta frequenze di campionamento comprese tra 5000 e 45000 Hz; eventualmente, sox fornisce il parametro -r che permette appunto di specificare il sampling rate del file che si intende creare.

Come al solito, se il formato audio sorgente non è supportato da sox, possiamo prima procedere alla sua conversione in Ogg Vorbis attraverso altri programmi, come VLC; vediamo, ad esempio, come ottenere un file WAV a partire dalla traccia audio di un video su YouTube. Prima di tutto, utilizziamo il programma gratuito youtube-dl per scaricare la traccia audio; con VLC, convertiamo la traccia audio in formato Ogg Vorbis e, infine, creiamo il file WAV con sox, come è stato già illustrato in precedenza.

Il nome del file WAV deve essere lungo al massimo 8 caratteri, più l'estensione .WAV, come richiesto dal DOS.

Bibliografia

Intel - 8237A High Performance Programmable DMA Controller
(disponibile nella sezione Documentazione tecnica di supporto al corso assembly dell’ Area Downloads di questo sito - 8237A_DMA.pdf)