Assembly Avanzato con NASM
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:
- una periferica invia una richiesta di trasferimento dati al DMAC
- il DMAC chiede alla CPU la disponibilità dei bus di sistema
- la CPU cede il controllo dei bus di sistema al DMAC
- il DMAC esegue il trasferimento dati richiesto dalla periferica
- il DMAC restituisce il controllo dei bus di sistema alla CPU
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:
- l'hard disk chiede l'intervento del DMAC attivando la linea DREQ
(DMA Request) a cui è connesso
- il DMAC attiva la linea HRQ (Hold Request) della CPU
per chiedere la disponibilità del Data Bus, dell'Address Bus e del
Control Bus
- la CPU cede il controllo del Data Bus, dell'Address Bus e
del Control Bus al DMAC e attiva la linea HLDA (Hold
Acknowledge)
- il DMAC attiva le linee IOR (I/O
Read = lettura da una periferica) e MEMW
(Memory Write = scrittura in memoria)
- il DMAC mette l'indirizzo di scrittura sull'Address Bus e attiva
la linea DACK (DMA Acknowledge) per dare il via libera all'hard
disk
- l'hard disk mette sul Data Bus il primo BYTE da trasferire
- il DMAC legge il BYTE e lo trasferisce in memoria
- il DMAC incrementa di 1 l'indirizzo sull'Address Bus e
decrementa di 1 il suo contatore interno
- il procedimento appena descritto continua finché il contatore si azzera
- il DMAC segnala all'hard disk la fine del trasferimento dati
- l'hard disk disattiva la linea DREQ
- il DMAC disattiva la linea DACK in modo che l'hard disk rilasci il
Data Bus
- il DMAC disabilita le sue linee di controllo e disattiva la linea
HRQ
- la CPU disattiva la linea HLDA e riprende il controllo totale dei
bus
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.
- 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; all'arrivo del fronte di
discesa del segnale ADSTB (Address Strobe), tali 8 bit
vengono prelevati dal Data Bus interno e spostati nell'8-bit latch.
Gli 8 bit meno significativi dell'indirizzo a 16 bit vengono
generati direttamente attraverso le otto linee interne A0-A3 e
A4-A7.
- Durante lo stato S2, il nuovo indirizzo a 16 bit appena generato
viene caricato sull'Address Bus; tale caricamento viene effettuato
attivando il segnale AEN (Address Enable).
- Durante lo stato S3, il DMAC verifica se sia necessario prolungare
i tempi di attesa prima che venga effettuata una lettura o scrittura; eventualmente,
dopo S3 viene inserito un numero adeguato di stati di attesa SW.
- Durante lo stato S4, viene effettuata l'effettiva lettura o scrittura del
prossimo BYTE (o WORD) da trasferire.
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.
- CURRENT WORD COUNT Register
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.
- Clear First/Last Flip-Flop
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:
- predisposizione di un buffer di memoria contenente i dati da trasferire
- installazione di una ISR per intercettare le IRQ generate
dalla periferica
- programmazione del DMAC 8237A
- programmazione della periferica
- avvio del trasferimento dati
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:
- masking del canale da utilizzare tramite il registro MASK
- invio del comando Clear First/Last Flip-Flop
- caricamento dell'indirizzo LSB + MSB nel registro CURRENT
ADDRESS
- caricamento della pagina di memoria nel registro PAGE
- caricamento del contatore LSB + MSB nel registro CURRENT
WORD COUNT
- impostazione della modalità e del tipo di trasferimento dati tramite il
registro MODE
- unmasking del canale da utilizzare tramite il registro MASK
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.
- 41h - Set digitized sound output sampling rate
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.
- Bxh - Program 16-bit DMA Mode digitized sound I/O
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.
- Cxh - Program 8-bit DMA Mode digitized sound I/O
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:
- invio del comando 41h - Set digitized sound output sampling rate
- invio del parametro "frequenza di campionamento" (MSB+LSB)
- invio del comando C0h - Program 8-bit DMA Mode digitized sound I/O,
DAC (output), Single Cycle
- invio del primo parametro "20h" - 8-bit stereo unsigned PCM
- invio del secondo parametro "numero di campioni da eseguire meno 1"
(LSB+MSB)
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 | 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 | 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à:
- formato PCM non compresso a 8 bit mono
- formato PCM non compresso a 8 bit stereo
- formato PCM non compresso a 16 bit mono
- formato PCM non compresso a 16 bit stereo
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)