Assembly Base con NASM
Capitolo 8: Struttura hardware della memoria
L'evoluzione tecnologica che interessa il mondo del computer, sta assumendo col passare degli
anni ritmi sempre più vertiginosi; nel corso di questa evoluzione, uno dei componenti che ha
acquisito una importanza sempre maggiore è sicuramente la memoria, al punto che, al giorno
d'oggi, non la si considera più come una delle tante periferiche, ma come una delle parti
fondamentali che insieme alla CPU formano il cuore del computer stesso. Lo scopo della
memoria è quello di immagazzinare informazioni in modo temporaneo o permanente; questa
distinzione porta a classificare le memorie in due grandi categorie:
- Memorie di lavoro
- Memorie di massa
Le memorie di lavoro vengono così chiamate in quanto sono destinate a contenere
temporaneamente tutte le informazioni (dati e istruzioni) che devono essere sottoposte ad
elaborazione da parte del computer; la fase di elaborazione deve svolgersi nel più breve
tempo possibile e quindi è fondamentale che le memorie di lavoro garantiscano velocità
di accesso elevatissime nelle operazioni di I/O. Proprio per tale motivo, queste
particolari memorie vengono realizzate mediante dispositivi elettronici a semiconduttore
che, come abbiamo visto nel Capitolo 5, presentano tempi di risposta estremamente ridotti;
si tenga presente comunque che la velocità operativa delle memorie di lavoro non può
certo competere con le prestazioni vertiginose delle attuali CPU.
Le memorie di massa hanno lo scopo di immagazzinare in modo permanente grosse quantità di
informazioni che possono essere utilizzate quando se ne ha bisogno; nel momento in cui si ha
la necessità di elaborare queste informazioni, si procede al loro caricamento nella memoria
di lavoro.
Le memorie di massa vengono realizzate principalmente ricorrendo a supporti ricoperti di
materiale magnetico (ossidi di ferro) che attraverso un opportuno circuito elettrico possono
essere facilmente magnetizzati (scritti) e smagnetizzati (cancellati). Ad esempio, gli Hard
Disk sono costituiti da uno o più dischi di alluminio ricoperti da ossido di ferro, mentre
gli ormai abbandonati Floppy Disk sono costituiti da un disco di materiale plastico ricoperto
sempre da ossido di ferro; uno dei punti di forza dei supporti magnetici è dato sicuramente
dalla enorme capacità di memorizzazione (centinaia di GiB), mentre lo svantaggio principale è
dato dalla velocità di accesso non molto elevata.
Un altro tipo di memoria di massa è rappresentato dai Compact Disc e dai Digital
Versatile Disc, scrivibili e riscrivibili attraverso tecnologie laser; i CD/DVD sono
caratterizzati da discrete velocità di accesso, mentre la capacità di immagazzinare
informazioni appare insufficiente.
In questo capitolo ci occuperemo delle memorie di lavoro che verranno analizzate dal
punto di vista hardware; nel capitolo successivo, invece, si parlerà della memoria di
lavoro dal punto di vista del programmatore.
8.1 Configurazione delle memorie di lavoro
Prima di analizzare la configurazione interna delle memorie di lavoro, è necessario
affrontare un aspetto molto delicato che induce spesso in errore i programmatori meno
esperti; questo errore molto frequente viene chiamato in gergo: fuori di uno.
Supponiamo, ad esempio, di voler recintare un lato di un terreno lungo 9 metri
disponendo una serie di paletti ad 1 metro di distanza l'uno dall'altro; la
domanda che ci poniamo è: quanti paletti sono necessari?
Molte persone rispondono istintivamente 9, dimenticando così il paletto iniziale che
potremmo definire come paletto numero 0; la Figura 8.1 chiarisce questa situazione.
Come si può notare dalla Figura 8.1, assegnando l'indice 0 al primo paletto, il secondo
paletto avrà indice 1, il terzo 2, il quarto 3 e così via, sino
all'ultimo paletto (il decimo) che avrà indice 9; complessivamente abbiamo bisogno
di 9 paletti (dal numero 1 al numero 9) più il paletto iniziale (il
numero 0) per un totale di 10 paletti. Questa situazione trae in inganno molte
persone che leggendo l'indice 9 sull'ultimo paletto, arrivano alla conclusione che
siano presenti solo 9 paletti; questa valutazione errata è dovuta al fatto che gli
indici partono da 0 anziché da 1.
È importantissimo capire questo concetto in quanto nel mondo del computer ci si imbatte
spesso in situazioni di questo genere; nei precedenti capitoli abbiamo visto numerosi
esempi di sequenze di elementi indicizzati a partire da 0. Consideriamo, ad esempio,
un computer con Address Bus a 16 linee; abbiamo visto che per convenzione la
prima linea dell'Address Bus viene indicata con A0 anziché A1, la
seconda linea viene indicata con A1 anziché A2 e così via, sino all'ultima
linea (la sedicesima) che viene indicata con A15 anziché A16.
Complessivamente abbiamo quindi 15 linee (da A1 ad A15) più la linea
di indice 0 (A0) per un totale di 16 linee.
Con 16 linee possiamo indirizzare 216=65536 celle di memoria; se
assegnamo alla prima cella l'indirizzo 0, allora la seconda cella avrà indirizzo
1, la terza 2, la quarta 3 e così via, sino all'ultima cella che
avrà indirizzo 65535 e non 65536. Complessivamente possiamo indirizzare
65535 celle (dalla 1 alla 65535) più la cella di indice 0
per un totale di 65536 celle.
Questa convenzione, largamente utilizzata nel mondo del computer, permette di ottenere enormi
semplificazioni, sia software che hardware; osserviamo, ad esempio, che con il precedente
Address Bus a 16 linee, la prima cella di memoria si trova all'indirizzo
binario 0000000000000000b (0d), mentre l'ultima cella si trova all'indirizzo
binario 1111111111111111b (65535d). Se avessimo fatto partire gli indici da
1, allora la prima cella si sarebbe trovata all'indirizzo binario
0000000000000001b (1d), mentre l'ultima cella si sarebbe trovata all'indirizzo
binario 10000000000000000b (65536d); quest'ultimo indirizzo è formato da
17 bit e ci costringerebbe ad usare un Address Bus a 17 linee!
La situazione appena descritta si presenta spesso anche nella scrittura dei programmi;
nel linguaggio C, ad esempio, gli indici dei vettori partono da 0 e non da
1. Supponiamo allora di avere la seguente definizione:
int vett[10]; /* vettore di 10 elementi di tipo int */
Il primo elemento di questo vettore è rappresentato da vett[0] e, di conseguenza,
l'ultimo elemento (il decimo) è vett[9] e non vett[10]; complessivamente
abbiamo 9 elementi (da vett[1] a vett[9]) più l'elemento di indice
0 (vett[0]) per un totale di 10 elementi.
In definitiva quindi, se abbiamo una generica sequenza di n elementi e assegnamo
l'indice 0 al primo elemento, allora l'ultimo elemento avrà indice n-1
e non n (l'elemento di indice n quindi non esiste); complessivamente avremo
n-1 elementi (dal numero 1 al numero n-1) più l'elemento di indice
0 per un totale di n elementi.
Dopo queste importanti precisazioni, possiamo passare ad analizzare l'organizzazione
interna delle memorie di lavoro; tutte le considerazioni che seguono si riferiscono come
al solito alle piattaforme hardware basate sulle CPU della famiglia 80x86.
La CPU vede la memoria di lavoro come un vettore di celle, cioè come una
sequenza di celle consecutive e contigue; come già sappiamo, ciascuna cella ha una
ampiezza pari a 8 bit. Nel caso, ad esempio, di una memoria di lavoro formata da
32 celle, indicando una generica cella con il simbolo Ck
(k compreso tra 0 e 31) si verifica la situazione mostrata in
Figura 8.2.
Come si può notare, ogni cella viene individuata univocamente dal relativo indice che
coincide esattamente con l'indirizzo fisico della cella stessa; se la CPU vuole
accedere ad una di queste celle, deve specificare il relativo indirizzo che viene poi
caricato sull'Address Bus. Se vogliamo accedere, ad esempio, alla trentesima cella,
dobbiamo caricare sull'Address Bus l'indirizzo 29 (ricordiamoci che gli
indici partono da 0); a questo punto la cella C29 viene messa
in collegamento con la CPU che può così iniziare il trasferimento dati attraverso
il Data Bus.
Il problema che si presenta è dato dal fatto che, come è stato detto nel precedente
capitolo, qualunque operazione effettuata dal computer deve svolgersi in un intervallo
di tempo ben definito e cioè, in un numero ben definito di cicli di clock; questo
discorso vale naturalmente anche per gli accessi alla memoria di lavoro. In sostanza,
l'accesso da parte della CPU ad una qualsiasi cella di memoria deve svolgersi in
un intervallo di tempo costante, indipendente quindi dalla posizione in memoria (indice)
della cella stessa; per soddisfare questa condizione, le memorie di lavoro vengono
organizzate sotto forma di matrice di celle. Una matrice non è altro che una tabella
di elementi disposti su m righe e n colonne; il numero complessivo di
elementi è rappresentato quindi dal prodotto m*n. Volendo organizzare, ad
esempio, il vettore di Figura 8.2 sotto forma di matrice di celle con 8 righe e
4 colonne (8*4=32), si ottiene la situazione mostrata in Figura 8.3.
Osserviamo subito che le 8 righe vengono individuate attraverso gli indici da
0 a 7 (indici di riga), mentre le 4 colonne vengono individuate
attraverso gli indici da 0 a 3 (indici di colonna); ciascun elemento della
matrice viene univocamente individuato attraverso i corrispondenti indice di riga e
indice di colonna che costituiscono le coordinate dell'elemento stesso.
Questa organizzazione a matrice permette alla CPU di accedere a qualsiasi cella
di memoria in un intervallo di tempo costante; la CPU non deve fare altro che
specificare l'indice di riga e l'indice di colonna della cella desiderata. Questi due
indici abilitano la cella che si trova all'incrocio tra la riga e la colonna specificate
dalla CPU; appare evidente che con questa tecnica, l'accesso ad una cella qualunque
richiede un intervallo di tempo costante e quindi assolutamente indipendente dalla
posizione della cella stessa.
In precedenza però è stato detto che la CPU vede la memoria sotto forma di
vettore di celle; dobbiamo capire quindi come sia possibile ricavare un indice di riga
e un indice di colonna da un indirizzo lineare destinato ad un vettore come quello di
Figura 8.2.
A tale proposito osserviamo innanzi tutto che il vettore di Figura 8.2 può essere
convertito nella matrice di Figura 8.3 raggruppando le celle a 4 a 4; le
celle da C0 a C3 formano la prima riga della
matrice, le celle da C4 a C7 formano la seconda
riga della matrice e così via. In questo modo è semplicissimo passare da una cella
della matrice di Figura 8.3 alla corrispondente cella del vettore di Figura 8.2; data,
ad esempio, la generica cella Ci,j della matrice di Figura 8.3, l'indice
k della corrispondente cella del vettore di Figura 8.2 si ricava dalla seguente
formula:
k = (i * 4) + j
(4 rappresenta il numero di colonne della matrice).
Consideriamo, ad esempio, C2,1 che è la decima cella della matrice di
Figura 8.3; la corrispondente cella del vettore di Figura 2 è quella individuata
dall'indice:
(2 * 4) + 1 = 8 + 1 = 9
Infatti, C9 è proprio la decima cella del vettore di Figura 8.2.
Vediamo ora come si procede per la conversione inversa; dato cioè l'indice k
di una cella del vettore di Figura 8.2, dobbiamo ricavare l'indice di riga i e
l'indice di colonna j della corrispondente cella della matrice di Figura 8.3.
Osserviamo subito che per indirizzare 32 celle abbiamo bisogno di un
Address Bus a log232=5 linee; con 5 linee, infatti,
possiamo rappresentare 25=32 indirizzi (da 0 a 31).
Siccome la matrice di Figura 8.3 è formata da 8 righe e 4 colonne,
suddividiamo le 5 linee dell'Address Bus in due gruppi; il primo gruppo
è formato da log28=3 linee (A2, A3 e A4),
mentre il secondo gruppo è formato da log24=2 linee (A0 e
A1). Questi due gruppi di linee ci permettono di individuare l'indice di riga
e l'indice di colonna della cella desiderata; per dimostrarlo ripetiamo il precedente
esempio. Vogliamo accedere quindi alla cella C9 del vettore di Figura
8.2; in binario (a 5 bit) l'indirizzo 9 si scrive 01001b. Questo
indirizzo viene caricato sull'Address Bus per cui otteniamo A0=1,
A1=0, A2=0, A3=1, A4=0. Le tre linee da A2 a A4
contengono il codice binario 010b che tradotto in base 10 rappresenta il
valore 2; questo valore rappresenta l'indice di riga. Le due linee da A0
a A1 contengono il codice binario 01b che tradotto in base 10
rappresenta il valore 1; questo valore rappresenta l'indice di colonna. La
cella individuata nella matrice di Figura 8.3 sarà quindi C2,1;
questa cella corrisponde proprio alla cella C9 del vettore di Figura
8.2.
Vediamo un ulteriore esempio relativo all'accesso alla cella C31 che
è l'ultima cella del vettore di Figura 8.2; in binario (a 5 bit) l'indirizzo
31 si scrive 11111b. Questo indirizzo viene caricato sull'Address Bus
per cui otteniamo A0=1, A1=1, A2=1, A3=1, A4=1. Le tre
linee da A2 a A4 contengono il codice binario 111b che tradotto in
base 10 rappresenta il valore 7; questo valore rappresenta l'indice di riga.
Le due linee da A0 a A1 contengono il codice binario 11b che tradotto
in base 10 rappresenta il valore 3; questo valore rappresenta l'indice di
colonna. La cella individuata nella matrice di Figura 8.3 sarà quindi C7,3;
come si può notare in Figura 8.3, questa è proprio l'ultima cella della matrice.
Traducendo in pratica le considerazioni appena svolte, otteniamo la situazione mostrata
in Figura 8.4; questo circuito si riferisce ad un computer con una memoria di lavoro da
32 byte, Address Bus a 5 linee e Data Bus a 8 linee.
La parte di indirizzo contenuta nelle linee A2, A3 e A4, raggiunge il
circuito chiamato Decodifica Riga; questo circuito provvede a ricavare l'indice di
riga della cella da abilitare. La parte di indirizzo contenuta nelle linee A0 e
A1, raggiunge il circuito chiamato Decodifica Colonna; questo circuito
provvede a ricavare l'indice di colonna della cella da abilitare. L'unica cella che viene
abilitata è quella che si trova all'incrocio tra i due indici appena determinati; a questo
punto il Data Bus viene connesso alla cella abilitata rendendo così possibile il
trasferimento dati tra la cella stessa e la CPU.
Nella parte destra della Figura 8.4 si nota la presenza del Data Bus a 8 linee
e della logica di controllo che comprende le tre linee CS, W e R;
lungo la linea CS transita un segnale chiamato Chip Select (selezione
chip di memoria). Se CS=1 la memoria di lavoro viene abilitata e la CPU
può comunicare con essa attraverso il Data Bus; se CS=0 la memoria di lavoro
è disabilitata e la CPU può comunicare con altri dispositivi di I/O
attraverso il Data Bus.
Se viene richiesto un accesso in scrittura (write) alla memoria di lavoro, la logica
di controllo invia i due segnali W=1 e R=0 (con CS=1); in questo modo,
la porta AND di sinistra produce in uscita un livello logico 1 che abilita
il Controllo Scrittura, mentre la porta AND di destra produce in uscita un
livello logico 0 che disabilita il Controllo Lettura.
Se viene richiesto un accesso in lettura (read) alla memoria di lavoro, la logica
di controllo invia i due segnali W=0 e R=1 (con CS=1); in questo modo,
la porta AND di sinistra produce in uscita un livello logico 0 che disabilita
il Controllo Scrittura, mentre la porta AND di destra produce in uscita un
livello logico 1 che abilita il Controllo Lettura.
8.1.1 Accesso in memoria con Data Bus a 8 bit
La situazione mostrata dalla Figura 8.4 è molto semplice grazie al fatto che l'ampiezza del
Data Bus (8 bit) coincide esattamente con l'ampiezza in bit di ogni cella di
memoria; in questo caso le 8 linee del Data Bus vengono connesse agli 8
bit dell'unica cella di memoria abilitata. Per chiarire meglio questo aspetto osserviamo la
Figura 8.5 che mostra le prime 8 celle del vettore di memoria di Figura 8.2.
In questo esempio il Data Bus risulta connesso alla cella C3
(quarta cella) della memoria di lavoro; com'era prevedibile, la linea D0 risulta
connessa al bit in posizione 0 di C3, la linea D1 risulta
connessa al bit in posizione 1 di C3 e così via, sino alla linea
D7 che risulta connessa al bit in posizione 7 di C3. Un
Data Bus a 8 linee può essere posizionato su una qualunque cella secondo
lo schema visibile in Figura 8.5; è assolutamente impossibile che il Data Bus possa
posizionarsi a cavallo tra due celle.
Appare evidente il fatto che con un Data Bus a 8 linee è possibile gestire
via hardware trasferimenti di dati aventi una ampiezza massima di 8 bit; se vogliamo
trasferire un blocco di dati più grande di 8 bit, dobbiamo suddividerlo in gruppi
di 8 bit. Un dato avente una ampiezza di 8 bit viene chiamato BYTE (da
non confondere con l'unità di misura byte); il termine BYTE definisce quindi
un tipo di dato che misura 1 byte, ossia 8 bit.
L'ampiezza in bit del Data Bus definisce anche la cosiddetta parola del
computer; nel caso di Figura 8.4 e di Figura 8.5 abbiamo a che fare quindi con un computer
avente parola di 8 bit.
8.1.2 Accesso in memoria con Data Bus a 16 bit
Passiamo ora al caso di un computer avente parola di 16 bit, cioè Data
Bus a 16 linee; il problema che si presenta è dato dal fatto che la memoria
di lavoro è suddivisa fisicamente in celle da 8 bit, mentre il Data Bus ha
una ampiezza di 16 bit. Per risolvere questo problema, la memoria di lavoro viene
suddivisa logicamente in coppie di celle adiacenti; nel caso, ad esempio, del vettore di
memoria di Figura 8.2, otteniamo le coppie (C0, C1),
(C2, C3), (C4, C5)
e così via, sino alla coppia (C30, C31).
Un Data Bus a 16 linee può essere posizionato su una qualunque di queste
coppie; è assolutamente impossibile quindi che il Data Bus possa posizionarsi a
cavallo tra due coppie di celle. Per chiarire meglio questo aspetto osserviamo la Figura
8.6 che mostra le prime 8 celle del vettore di memoria di Figura 8.2, suddivise in
coppie.
In questo esempio il Data Bus risulta connesso alla coppia (C2,
C3) della memoria di lavoro; le linee da D0 a D7 risultano
connesse alla cella C2, mentre le linee da D8 a D15
risultano connesse alla cella C3.
Dalla Figura 8.6 si deduce che con un Data Bus a 16 linee è possibile gestire
via hardware trasferimenti di dati aventi una ampiezza di 8 bit o di 16 bit;
se vogliamo trasferire un blocco di dati più grande di 16 bit, dobbiamo suddividerlo
in gruppi di 8 o 16 bit. In precedenza abbiamo visto che un dato avente una
ampiezza di 8 bit viene chiamato BYTE; un dato avente ampiezza di 16
bit, invece, viene chiamato WORD. Non bisogna confondere il termine WORD con
l'unità di misura word; infatti, il termine WORD indica un tipo di dato che
misura 1 word, ossia 2 byte, ossia 16 bit.
Come già sappiamo, l'indirizzo di un dato di tipo BYTE coincide con l'indirizzo
della cella che lo contiene; qual è, invece, l'indirizzo di un dato di tipo WORD?
Per rispondere a questa domanda bisogna tenere presente che le CPU della famiglia
80x86, nel disporre i dati in memoria, seguono una convenzione chiamata
little-endian; questa convenzione prevede che i dati formati da due o più
BYTE vengano disposti in memoria con il BYTE meno significativo che
occupa l'indirizzo più basso.
Supponiamo, ad esempio, di avere il dato a 16 bit 0111001100001111b che si
trova in memoria a partire dall'indirizzo 350; la convenzione little-endian
prevede che questo dato venga disposto in memoria secondo lo schema mostrato in Figura 8.7.
Come si può notare, il BYTE meno significativo 00001111b viene disposto
nella cella 350, mentre il BYTE più significativo 01110011b viene
disposto nella cella 351; altre CPU come, ad esempio, quelle prodotte dalla
Motorola, utilizzano invece la convenzione inversa (big-endian).
La Figura 8.7 ci permette anche di osservare che se vogliamo tracciare uno schema della
memoria su un foglio di carta, ci conviene disporre gli indirizzi in ordine crescente
da destra verso sinistra; in questo modo i dati binari (o esadecimali) contenuti nelle
varie celle ci appaiono disposti nel verso giusto (cioè, con il peso delle cifre che
cresce da destra verso sinistra).
Analizziamo ora come avviene il trasferimento dati di tipo BYTE e WORD con
un Data Bus a 16 linee.
In riferimento alla Figura 8.6, supponiamo di voler leggere il BYTE contenuto nella
cella C2; il Data Bus viene posizionato sulla coppia
(C2, C3) e l'accesso alla cella C2
avviene attraverso le linee da D0 a D7.
Supponiamo di voler leggere il BYTE contenuto nella cella C3; il
Data Bus viene posizionato sulla coppia(C2, C3)
e l'accesso alla cella C3 avviene attraverso le linee da D8 a
D15.
Supponiamo di voler leggere il BYTE contenuto nella cella C4;
il Data Bus viene posizionato sulla coppia (C4,
C5) e l'accesso alla cella C4 avviene attraverso le
linee da D0 a D7.
Le considerazioni appena svolte ci fanno capire che con un Data Bus a 16
linee i dati di tipo BYTE non presentano alcun problema di allineamento
in memoria; questo significa che un dato di tipo BYTE, indipendentemente dal suo
indirizzo, richiede un solo accesso in memoria per essere letto o scritto.
In riferimento alla Figura 8.6, supponiamo di voler leggere la WORD contenuta nelle
celle C2 e C3; il Data Bus viene posizionato
sulla coppia (C2, C3) e l'accesso alle celle
C2 e C3 avviene attraverso le linee da D0 a
D15.
Supponiamo di voler leggere la WORD contenuta nelle celle C3
e C4; il Data Bus viene prima posizionato sulla coppia
(C2, C3) e attraverso le linee da D8 a
D15 viene letto il BYTE che si trova nella cella C3.
Successivamente, il Data Bus viene posizionato sulla coppia (C4,
C5) e attraverso le linee da D0 a D7 viene letto il
BYTE che si trova nella cella C4; complessivamente vengono
effettuati due accessi in memoria.
Le considerazioni appena svolte ci fanno capire che con un Data Bus a 16
linee i dati di tipo WORD non presentano alcun problema di allineamento
in memoria purché il loro indirizzo sia un numero pari (0, 2, 4,
6, etc); un dato di tipo WORD che si trova ad un indirizzo dispari,
richiede due accessi in memoria per essere letto o scritto!
Si tenga presente che il disallineamento dei dati in memoria può provocare sensibili
perdite di tempo anche con le potentissime CPU dell'ultima generazione; proprio
per evitare questi problemi, l'Assembly e molti linguaggi di programmazione di
alto livello forniscono ai programmatori tutti gli strumenti necessari per allineare
correttamente i dati in memoria.
8.1.3 Accesso in memoria con Data Bus a 32 bit
Anche se il meccanismo dovrebbe essere ormai chiaro, analizziamo un ulteriore caso che si
riferisce ad un computer avente parola di 32 bit, cioè Data Bus a
32 linee; in questo caso il problema da affrontare riguarda il fatto che la memoria
di lavoro è suddivisa fisicamente in celle da 8 bit, mentre il Data Bus ha
una ampiezza di 32 bit. Per risolvere questo problema, la memoria di lavoro viene
suddivisa logicamente in quaterne di celle adiacenti; nel caso, ad esempio, del vettore di
memoria di Figura 8.2, otteniamo le quaterne:
(C0, C1, C2, C3),
(C4, C5, C6, C7)
e cosi via, sino alla quaterna
(C28, C29, C30, C31).
Un Data Bus a 32 linee può essere posizionato su una qualunque di queste
quaterne; è assolutamente impossibile quindi che il Data Bus possa posizionarsi a
cavallo tra due quaterne di celle. Per chiarire meglio questo aspetto osserviamo la Figura
8.8 che mostra le prime 8 celle del vettore di memoria di Figura 8.2, suddivise in
quaterne.
In questo esempio il Data Bus risulta connesso alla quaterna (C0,
C1, C2, C3) della memoria di lavoro;
le linee da D0 a D7 risultano connesse alla cella C0, le
linee da D8 a D15 risultano connesse alla cella C1, le
linee da D16 a D23 risultano connesse alla cella C2 e le
linee da D24 a D31 risultano connesse alla cella C3.
Dalla Figura 8.8 si deduce che con un Data Bus a 32 linee è possibile gestire
via hardware trasferimenti di dati aventi una ampiezza di 8 bit, di 16 bit o
di 32 bit; se vogliamo trasferire un blocco di dati più grande di 32 bit,
dobbiamo suddividerlo in gruppi di 8, 16 o 32 bit. Un dato avente
ampiezza di 32 bit viene chiamato DWORD (da non confondere con l'unità di
misura dword o double word); il termine DWORD indica quindi un tipo
di dato che misura 1 dword, ossia 2 word, ossia 4 byte, ossia
32 bit.
Analizziamo ora come avviene il trasferimento dati di tipo BYTE, WORD e
DWORD con un Data Bus a 32 linee.
In riferimento alla Figura 8.8, supponiamo di voler leggere il BYTE contenuto nella
cella C0; il Data Bus viene posizionato sulla quaterna
(C0, C1, C2, C3)
e l'accesso alla cella C0 avviene attraverso le linee da D0 a
D7.
Supponiamo di voler leggere il BYTE contenuto nella cella C1; il
Data Bus viene posizionato sulla quaterna (C0, C1,
C2, C3) e l'accesso alla cella C1
avviene attraverso le linee da D8 a D15.
Supponiamo di voler leggere il BYTE contenuto nella cella C2; il
Data Bus viene posizionato sulla quaterna (C0, C1,
C2, C3) e l'accesso alla cella C2
avviene attraverso le linee da D16 a D23.
Supponiamo di voler leggere il BYTE contenuto nella cella C3; il
Data Bus viene posizionato sulla quaterna (C0, C1,
C2, C3) e l'accesso alla cella C3
avviene attraverso le linee da D24 a D31.
Supponiamo di voler leggere il BYTE contenuto nella cella C4; il
Data Bus viene posizionato sulla quaterna (C4, C5,
C6, C7) e l'accesso alla cella C4
avviene attraverso le linee da D0 a D7.
Anche in questo caso si nota subito che con un Data Bus a 32 linee i dati di
tipo BYTE non presentano alcun problema di allineamento in memoria; un dato di
tipo BYTE, indipendentemente dal suo indirizzo, richiede quindi un solo accesso in
memoria per essere letto o scritto.
In riferimento alla Figura 8.8, supponiamo di voler leggere la WORD contenuta nelle
celle C0 e C1; il Data Bus viene posizionato
sulla quaterna (C0, C1, C2,
C3) e l'accesso alle celle C0 e C1
avviene attraverso le linee da D0 a D15.
Supponiamo di voler leggere la WORD contenuta nelle celle C1 e
C2; il Data Bus viene posizionato sulla quaterna
(C0, C1, C2, C3)
e l'accesso alle celle C1 e C2 avviene attraverso le
linee da D8 a D23.
Supponiamo di voler leggere la WORD contenuta nelle celle C2 e
C3; il Data Bus viene posizionato sulla quaterna
(C0, C1, C2, C3)
e l'accesso alle celle C2 e C3 avviene attraverso le
linee da D16 a D31.
Supponiamo di voler leggere la WORD contenuta nelle celle C3 e
C4; il Data Bus viene prima posizionato sulla quaterna
(C0, C1, C2, C3)
e attraverso le linee da D24 a D31 viene letto il BYTE che si trova
nella cella C3. Successivamente, il Data Bus viene posizionato
sulla quaterna (C4, C5, C6,
C7) e attraverso le linee da D0 a D7 viene letto il
BYTE che si trova nella cella C4; complessivamente vengono
effettuati due accessi in memoria.
Le considerazioni appena svolte ci fanno capire che con un Data Bus a 32
linee i dati di tipo WORD non presentano alcun problema di allineamento
in memoria purché siano interamente contenuti all'interno di una quaterna di celle;
se la WORD si trova a cavallo tra due quaterne, richiede due accessi in memoria
per essere letta o scritta. Un metodo molto semplice per evitare questo problema
consiste nel disporre i dati di tipo WORD ad indirizzi pari (0, 2,
4, 6, etc); in questo modo, infatti, il dato si viene a trovare, o nelle
prime due celle, o nelle ultime due celle di una quaterna.
In riferimento alla Figura 8.8, supponiamo di voler leggere la DWORD contenuta nelle
celle C0, C1, C2 e
C3; il Data Bus viene posizionato sulla quaterna
(C0, C1, C2, C3)
e l'accesso alle celle C0, C1, C2 e
C3 avviene attraverso le linee da D0 a D31.
Supponiamo di voler leggere la DWORD contenuta nelle celle C1,
C2, C3 e C4; il Data Bus viene
prima posizionato sulla quaterna (C0, C1,
C2, C3) e attraverso le linee da D8 a D31
vengono letti i 3 BYTE che si trovano nelle celle C1,
C2 e C3. Successivamente, il Data Bus viene
posizionato sulla quaterna (C4, C5, C6,
C7) e attraverso le linee da D0 a D7 viene letto il
BYTE che si trova nella cella C4; complessivamente vengono
effettuati due accessi in memoria.
La possibilità di leggere o scrivere un blocco di 3 BYTE è una caratteristica delle
CPU Intel con architettura a 32 bit; in questo modo si riduce a 2 il
numero massimo di accessi in memoria per la lettura o la scrittura di un dato di tipo
DWORD.
Le considerazioni appena svolte ci fanno capire che con un Data Bus a 32
linee i dati di tipo DWORD non presentano alcun problema di allineamento
in memoria purché siano interamente contenuti all'interno di una quaterna di celle;
se la DWORD si trova a cavallo tra due quaterne, richiede due accessi in memoria
per essere letta o scritta. Il modo più ovvio (e anche l'unico) per evitare questo
problema consiste nel disporre i dati di tipo DWORD ad indirizzi multipli interi
di 4 (0, 4, 8, 12, etc).
Dopo aver descritto la configurazione interna delle memorie di lavoro, possiamo passare
ad analizzare i vari tipi di memoria di lavoro e le tecniche che vengono utilizzate per la
fabbricazione dei circuiti di memoria; le memorie di lavoro vengono suddivise in due
categorie principali:
Prima di illustrare la struttura circuitale delle memorie RAM, dobbiamo fare la
conoscenza con particolari circuiti logici chiamati reti sequenziali.
8.2 Le reti sequenziali
Nei precedenti capitoli abbiamo visto che le reti combinatorie sono formate da componenti
come le porte logiche che presentano la caratteristica di fornire un segnale in uscita che
è una logica conseguenza del segnale (o dei segnali) in ingresso; se cambiano i livelli
logici dei segnali in ingresso, cambia (dopo un piccolo ritardo di propagazione) anche il
segnale in uscita e questo segnale non viene minimamente influenzato dalla precedente
configurazione ingresso/uscita assunta dalla porta. In pratica, si potrebbe dire che le
porte logiche "non ricordano" lo stato che avevano assunto in precedenza; molto spesso
però si ha la necessità di realizzare reti combinatorie formate da circuiti logici capaci
di ricordare gli stati precedenti, producendo quindi un segnale in uscita che è una logica
conseguenza, non solo dei nuovi segnali in ingresso, ma anche degli ingressi precedenti. Le
reti combinatorie dotate di queste caratteristiche prendono il nome di reti sequenziali
e vengono largamente impiegate nella realizzazione delle memorie di lavoro.
8.2.1 Il flip-flop set/reset
Per realizzare un circuito logico capace di ricordare gli stati assunti in precedenza, si
sfrutta una tecnica molto usata in campo elettronico, chiamata retroazione; questa
tecnica consiste nel prelevare il segnale in uscita da un dispositivo, riapplicandolo
all'ingresso del dispositivo stesso (o di un altro dispositivo). Applicando, ad esempio, la
"retroazione incrociata" a due porte NAND, si perviene al circuito logico di Figura
8.9a che rappresenta l'elemento fondamentale delle reti sequenziali e prende il nome di
flip-flop set/reset (abbreviato in FF-SR); la Figura 8.9b mostra il simbolo del
FF-SR che si utilizza negli schemi dei circuiti logici.
Per capire il principio di funzionamento del FF-SR bisogna ricordare che la porta
NAND produce in uscita un livello logico 0 solo quando tutti gli ingressi sono
a livello logico 1; in tutti gli altri casi si otterrà in uscita un livello logico
1.
Ponendo ora in ingresso S=1, R=1, si nota che le due uscite Q e Q'
assumono una configurazione del tutto casuale; si può constatare però che le due uniche
possibilità sono Q=0, Q'=1, oppure Q=1, Q'=0. Infatti, se la
porta che abbiamo indicato con A produce in uscita 1, attraverso la retroazione
questa uscita si porta all'ingresso della porta B che avendo entrambi gli ingressi a
1 produrrà in uscita 0; viceversa, se la porta A produce in uscita
0, questa uscita si porta all'ingresso della porta B che avendo in ingresso la
configurazione (0, 1) produrrà in uscita 1.
Partiamo quindi dalla configurazione R=1, S=1 e tenendo R=1 portiamo
l'ingresso S a 0; osservando la Figura 8.9a si vede subito che la porta
A, avendo un ingresso a 0, produrrà sicuramente in uscita un 1. La
porta B trovandosi in ingresso la coppia (1, 1), produrrà in uscita
0; a questo punto, qualsiasi modifica apportata a S (purché R resti a
1) non modificherà l'uscita Q che continuerà a valere 1. Abbiamo
appurato quindi che partendo dalla configurazione in ingresso (1, 1) e portando
in successione S prima a 0 e poi a 1, il FF-SR memorizza un bit che
vale 1 ed è disponibile sull'uscita Q; questa situazione permane inalterata
finché R continua a valere 1.
Portando ora R a 0 e tenendo S=1, si vede che la porta B, avendo
un ingresso a 0 produrrà in uscita 1, mentre la porta A ritrovandosi
in ingresso la coppia (1, 1) produrrà in uscita 0; finché S
rimane a 1 qualsiasi modifica apportata all'ingresso R non altera l'uscita
Q che continuerà a valere 0. Abbiamo appurato quindi che partendo dalla
configurazione in ingresso (1, 1) e portando in successione R prima a
0 e poi di nuovo a 1, il FF-SR memorizza un bit che vale 0 ed
è disponibile sull'uscita Q.
Da tutte le considerazioni appena esposte, risulta evidente che il FF-SR rappresenta
una unità elementare di memoria capace di memorizzare un solo bit; infatti:
- Attraverso l'ingresso S è possibile scrivere un 1 (set) nel
FF-SR
- Attraverso l'ingresso R è possibile scrivere uno 0 (reset)
nel FF-SR
- Attraverso l'uscita Q è possibile leggere il bit memorizzato nel
FF-SR
8.2.2 Il flip-flop data
A partire dal FF-SR è possibile realizzare diversi altri tipi di flip-flop che
trovano svariate applicazioni nell'elettronica digitale; in particolare, la Figura 8.10 mostra
il flip-flop tipo D (dove la D sta per Data o Delay), che
si presta particolarmente per la realizzazione di memorie di piccole dimensioni. La Figura 8.10a
mostra lo schema circuitale del flip-flop data (abbreviato in FF-D); la Figura
8.10b mostra il simbolo logico attraverso il quale si rappresenta il FF-D.
L'ingresso D viene chiamato Data ed è proprio da questo ingresso che arriva
il bit da memorizzare; l'ingresso CLK fornisce al FF un segnale di clock (di
cui si è parlato nel precedente capitolo) che permette di sincronizzare le operazioni di
I/O. In Figura 8.10a si nota anche un ingresso CKI che serve solo per mostrare
il principio di funzionamento del FF-D e che ha il compito di impedire (Clock
Inhibit) o permettere al segnale di clock di arrivare al FF; infine, le due uscite
Q e Q' sono le stesse del FF-SR visto prima.
Per descrivere il principio di funzionamento del FF-D notiamo innanzi tutto che,
finché l'ingresso CKI è a 0, la porta AND a sinistra produrrà in
uscita un livello logico 0 che andrà a raggiungere entrambe le porte NAND
indicate con A e B, che a loro volta produrranno in uscita un livello logico
1; le due porte NAND indicate con A' e B' costituiscono un
FF-SR che si troverà di conseguenza nella configurazione iniziale S=1,
R=1. Partendo da questa configurazione iniziale e abilitando l'ingresso CKI,
il segnale di clock può giungere alle due porte A e B e in un ciclo completo
(cioè con il segnale di clock che passa da 0 a 1 e poi di nuovo a 0) il
bit che arriva dall'ingresso D verrà copiato sull'uscita Q; riportando ora a
livello logico 0 l'ingresso CKI, il bit appena copiato sull'uscita Q
resterà "imprigionato" indipendentemente dal valore dell'ingresso D (per questo
motivo il FF-D viene anche chiamato latch, che in inglese significa lucchetto).
Per la dimostrazione delle cose appena dette ci possiamo servire della Figura 8.11: la Figura
8.11a si riferisce al caso D=1, mentre la Figura 8.11b si riferisce al caso D=0.
Cominciamo dal caso in cui dalla linea D arrivi un livello logico 1 (Figura
8.11a); non appena CKI passa da 0 a 1, il segnale di clock viene
abilitato e può giungere alle due porte A e B. Analizzando la Figura 8.10 e
la Figura 8.11a possiamo osservare che quando CLK passa da 0 a 1, le due
porte A e B lasciano R a 1 e portano S da 1 a
0; quando CLK passa da 1 a 0, le due porte A e B
lasciano R a 1 e portano S da 0 a 1. Applicando allora
le cose esposte in precedenza sul FF-SR possiamo dire che l'operazione appena
effettuata (set) ha quindi memorizzato (in un ciclo di clock) l'ingresso D=1
sul FF-SR; il bit memorizzato (1) è disponibile sull'uscita Q.
Passiamo ora al caso in cui dalla linea D arrivi un livello logico 0 (Figura
8.11b); non appena CKI passa da 0 a 1, il segnale di clock viene
abilitato e può giungere alle due porte A e B. Analizzando la Figura 8.10 e
la Figura 8.11b possiamo osservare che quando CLK passa da 0 a 1, le due
porte A e B lasciano S a 1 e portano R da 1 a
0; quando CLK passa da 1 a 0, le due porte A e B
lasciano S a 1 e portano R da 0 a 1. Applicando allora
le cose esposte in precedenza sul FF-SR possiamo dire che l'operazione appena
effettuata (reset) ha quindi memorizzato (in un ciclo di clock) l'ingresso D=0
sul FF-SR; il bit memorizzato (0) è disponibile sull'uscita Q.
8.2.3 Struttura circuitale di un FF
A titolo di curiosità analizziamo le caratteristiche costruttive di un FF destinato
a svolgere il ruolo di unità elementare di memoria; in Figura 8.12 vediamo, ad esempio, un
FF realizzato in tecnologia NMOS.
Per capire il funzionamento di questo circuito bisogna ricordare che il MOS è un
regolatore di corrente pilotato in tensione; regolando da 0 ad un massimo la
tensione applicata sul gate, si può regolare da 0 ad un massimo la corrente che
circola tra il drain e il source. Applicando al gate di un NMOS una tensione
nulla (o inferiore alla tensione di soglia), il canale tra drain e source è chiuso e
impedisce quindi il passaggio della corrente; in queste condizioni il transistor si
comporta come un interruttore aperto (OFF). Applicando al gate di un NMOS
una tensione superiore alla tensione di soglia, il canale tra drain e source è aperto
e favorisce quindi il passaggio della corrente; in queste condizioni il transistor si
comporta come un interruttore chiuso (ON).
Al centro della Figura 8.12 si nota il FF costituito dai due transistor T1 e
T2; i due transistor T3 e T4 svolgono semplicemente il ruolo di carichi
resistivi per T1 e T2. I due transistor T5 e T6 servono per
le operazioni di lettura e di scrittura; la linea RG infine serve per abilitare la
riga lungo la quale si trova la cella (o le celle) a cui vogliamo accedere.
Osserviamo subito che se T1 è ON, allora T2 deve essere per forza
OFF; viceversa se T1 è OFF, allora T2 deve essere per forza
ON. Infatti, se T1 è ON, allora la corrente che arriva dal positivo
di alimentazione (+Vcc) si riversa a massa proprio attraverso T1; questa
circolazione di corrente attraverso il carico resistivo T3 erode il potenziale
+Vcc portando verso lo 0 il potenziale del punto A e quindi anche il
potenziale del gate di T2 che viene spinto così allo stato OFF. Analogamente,
se T2 è ON, allora la corrente che arriva dal positivo di alimentazione
(+Vcc) si riversa a massa proprio attraverso T2; questa circolazione di
corrente attraverso il carico resistivo T4 erode il potenziale +Vcc portando
verso lo 0 il potenziale del punto B e quindi anche il potenziale del gate di
T1 che viene spinto così allo stato OFF.
Vediamo ora come si svolgono con il circuito di Figura 8.12 le tre operazioni fondamentali
e cioè: memorizzazione, lettura, scrittura:
Per la fase di memorizzazione (cioè per tenere il dato in memoria) si pone RG=0;
in questo modo, sia T5 che T6 sono OFF, per cui il FF risulta
isolato dall'esterno. Il valore del bit memorizzato nel FF è rappresentato dalla
tensione Vds che si misura tra il drain e il source di T1; se T1 è
ON allora Vds=0, mentre se T1 è OFF allora Vds=+Vcc.
In base a quanto è stato detto in precedenza si può affermare quindi che la configurazione
T1 ON e T2 OFF rappresenta un bit di valore 0 in memoria;
analogamente, la configurazione T1 OFF e T2 ON rappresenta un bit
di valore 1 in memoria.
Per la fase di lettura si pone RG=1; in questo modo, sia T5 che T6
sono ON. Se il FF memorizza un bit di valore 0 allora come già
sappiamo T1 è ON e T2 è OFF; in queste condizioni la corrente
che arriva dal positivo di alimentazione transita, sia attraverso T1, sia attraverso
T5 riversandosi anche lungo la linea D che segnala la lettura di un bit di
valore 0. Se il FF memorizza un bit di valore 1 allora come già
sappiamo T1 è OFF e T2 è ON; in queste condizioni la corrente
che arriva dal positivo di alimentazione transita, sia attraverso T2, sia attraverso
T6 riversandosi anche lungo la linea D' che segnala la lettura di un bit
di valore 1.
Per la fase di scrittura si pone RG=1; in questo modo, sia T5 che T6
sono ON. Per scrivere un bit di valore 0 si pone D=0 e D'=1;
in questo modo la corrente che arriva dal positivo di alimentazione fluisce verso D
attraverso T5. Il potenziale +Vcc viene eroso dal carico resistivo T3
e quindi il potenziale del punto A si porta verso lo zero spingendo T2 allo
stato OFF e T1 allo stato ON; come abbiamo visto in precedenza,
questa situazione rappresenta un bit di valore 0 in memoria. Per scrivere un bit di
valore 1 si pone D=1 e D'=0; in questo modo la corrente che arriva dal
positivo di alimentazione fluisce verso D' attraverso T6. Il potenziale
+Vcc viene eroso dal carico resistivo T4 e quindi il potenziale del punto
B si porta verso lo zero spingendo T1 allo stato OFF e T2 allo
stato ON; come abbiamo visto in precedenza, questa situazione rappresenta un bit
di valore 1 in memoria.
Appare evidente il fatto che il funzionamento dei FF è legato alla presenza della
alimentazione elettrica; non appena si toglie l'alimentazione elettrica, il contenuto dei
FF viene perso. Queste considerazioni si applicano naturalmente a tutte le memorie
di lavoro realizzate con i FF; possiamo dire quindi che non appena si spegne il
computer, tutto il contenuto delle memorie di lavoro di questo tipo viene perso.
Nei paragrafi seguenti vengono utilizzati i FF appena descritti per mostrare alcuni
esempi relativi ad importanti dispositivi di memoria come i registri e le RAM.
8.3 Registri di memoria
Abbiamo visto che il FF rappresenta una unità elementare di memoria capace di
memorizzare un bit di informazione; se si ha la necessità di memorizzare informazioni più
complesse costituite da numeri binari a due o più bit, bisogna collegare tra loro un numero
adeguato di FF, ottenendo così particolari circuiti logici chiamati registri.
8.3.1 Registri a scorrimento
Collegando più FF in serie, si ottiene la configurazione mostrata in Figura 8.13; in
particolare, questo esempio si riferisce ad un registro formato da 4 FF-D che
permette di memorizzare numeri a 4 bit (le uscite Q' sono state soppresse in
quanto non necessarie).
Per capire meglio il principio di funzionamento del circuito di Figura 8.13, bisogna tenere
presente che i FF vengono realizzati con le porte logiche; come abbiamo visto nei
precedenti capitoli, tutte le porte logiche presentano un certo ritardo di propagazione.
In sostanza, inviando dei segnali sugli ingressi di una porta logica, il conseguente
segnale di uscita viene ottenuto dopo un certo intervallo di tempo; questo ritardo è
dovuto come sappiamo ai tempi di risposta dei transistor utilizzati per realizzare le
porte logiche.
Nel circuito di Figura 8.13 i 4 bit da memorizzare arrivano in serie dall'ingresso
SI (serial input); in una situazione del genere è praticamente impossibile quindi
memorizzare un nibble in un solo ciclo di clock perché ciò richiederebbe una risposta
istantanea da parte di tutti i 4 FF. Per risolvere questo problema, la
frequenza del segnale di clock viene scelta in modo da minimizzare i tempi di attesa
dovuti al ritardo di propagazione; il metodo più efficace per ottenere questo risultato
consiste nel fare in modo che ad ogni ciclo di clock il registro di Figura 8.13 sia pronto
per ricevere un nuovo bit da memorizzare.
Supponiamo, ad esempio, di voler memorizzare nel circuito di Figura 8.13 il numero binario
1010b; i 4 bit di questo numero arrivano in serie dall'ingresso SI
a partire dal bit meno significativo. L'ingresso dei vari bit viene sincronizzato
attraverso il segnale di clock in modo che ad ogni ciclo di clock arrivi un nuovo bit da
memorizzare; analizziamo allora quello che accade attraverso la Figura 8.14.
Inizialmente il segnale di clock è inibito (CKI=0) e tutti i 4 FF-SR
contenuti nei 4 FF-D si troveranno quindi nella situazione iniziale S=1,
R=1; per semplicità supponiamo che le 4 uscite Q0, Q1, Q2
e Q3 siano tutte a livello logico 0 (anche se ciò non è necessario).
Come si può notare dalla Figura 8.13, il segnale di clock una volta abilitato (CKI=1)
giunge contemporaneamente a tutti i 4 FF-D; a questo punto si verifica la
seguente successione di eventi:
- Inizia la fase 1 (Figura 8.14) con il primo ciclo di clock che raggiunge
tutti i 4 FF
- Il bit di valore 0 giunge sull'ingresso D3 di FF3
- Ciascun FF copia sulla propria uscita Q il segnale che trova sul
proprio ingresso D
- Il FF3 copia il bit D3=0 sulla sua uscita Q3
- A causa del ritardo di propagazione, FF2 copia il vecchio D2=Q3=0
sulla sua uscita Q2
- A causa del ritardo di propagazione, FF1 copia il vecchio D1=Q2=0
sulla sua uscita Q1
- A causa del ritardo di propagazione, FF0 copia il vecchio D0=Q1=0
sulla sua uscita Q0
- Inizia la fase 2 (Figura 8.14) con il secondo ciclo di clock che raggiunge
tutti i 4 FF
- Il bit di valore 1 giunge sull'ingresso D3 di FF3
- Ciascun FF copia sulla propria uscita Q il segnale che trova sul
proprio ingresso D
- Il FF3 copia il bit D3=1 sulla sua uscita Q3
- A causa del ritardo di propagazione, FF2 copia il vecchio D2=Q3=0
sulla sua uscita Q2
- A causa del ritardo di propagazione, FF1 copia il vecchio D1=Q2=0
sulla sua uscita Q1
- A causa del ritardo di propagazione, FF0 copia il vecchio D0=Q1=0
sulla sua uscita Q0
- Inizia la fase 3 (Figura 8.14) con il terzo ciclo di clock che raggiunge
tutti i 4 FF
- Il bit di valore 0 giunge sull'ingresso D3 di FF3
- Ciascun FF copia sulla propria uscita Q il segnale che trova sul
proprio ingresso D
- Il FF3 copia il bit D3=0 sulla sua uscita Q3
- A causa del ritardo di propagazione, FF2 copia il vecchio D2=Q3=1
sulla sua uscita Q2
- A causa del ritardo di propagazione, FF1 copia il vecchio D1=Q2=0
sulla sua uscita Q1
- A causa del ritardo di propagazione, FF0 copia il vecchio D0=Q1=0
sulla sua uscita Q0
- Inizia la fase 4 (Figura 8.14) con il quarto ciclo di clock che raggiunge
tutti i 4 FF
- Il bit di valore 1 giunge sull'ingresso D3 di FF3
- Ciascun FF copia sulla propria uscita Q il segnale che trova sul
proprio ingresso D
- Il FF3 copia il bit D3=1 sulla sua uscita Q3
- A causa del ritardo di propagazione, FF2 copia il vecchio D2=Q3=0
sulla sua uscita Q2
- A causa del ritardo di propagazione, FF1 copia il vecchio D1=Q2=1
sulla sua uscita Q1
- A causa del ritardo di propagazione, FF0 copia il vecchio D0=Q1=0
sulla sua uscita Q0
Come si nota dalla Figura 8.14, quando CKI viene riportato a 0 disabilitando
il segnale di clock, si ottiene Q3=1, Q2=0, Q1=1, Q0=0; possiamo
dire quindi che dopo 4 cicli di clock il circuito di Figura 8.13 ha memorizzato il
nibble 1010b. Con CKI=0, qualunque sequenza di livelli logici in arrivo da
SI non altera più la configurazione assunta dal circuito di Figura 8.13.
Le considerazioni appena esposte evidenziano il fatto che nel corso dei 4 cicli di
clock, i bit da memorizzare scorrono da sinistra verso destra; per questo motivo il circuito
di Figura 8.13 viene anche chiamato registro a scorrimento.
Una volta capito il metodo di scrittura nei registri a scorrimento diventa facile capire
anche il metodo di lettura; basta, infatti, inviare altri 4 cicli di clock per far
scorrere verso destra i 4 bit precedentemente memorizzati. Questi 4 bit si
presentano quindi in sequenza (sempre a partire dal bit meno significativo) sull'uscita
SO (serial output); dalla Figura 8.13 si può constatare che è anche possibile
leggere i 4 bit direttamente dalle uscite Q0, Q1, Q2 e
Q3.
8.3.2 Registri paralleli
I registri a scorrimento presentano il vantaggio di avere una struttura relativamente
semplice in quanto richiedono un numero ridotto di collegamenti; come si nota, infatti, dalla
Figura 8.13, è presente un'unica linea per l'input (SI) e un'unica linea per l'output
(SO). Gli svantaggi abbastanza evidenti dei registri a scorrimento sono rappresentati,
invece, dal tempo di accesso in I/O e dal metodo di lettura che distrugge il dato in
memoria; per quanto riguarda i tempi di accesso in lettura/scrittura si può osservare che
il numero di cicli di clock necessari è pari al numero di FF che formano il registro.
Per quanto riguarda, invece, l'altro problema, risulta evidente che per leggere, ad esempio, il
contenuto di un registro a 4 bit, bisogna inviare 4 cicli di clock; in questo
modo escono dalla linea SO i 4 bit in memoria, ma dalla linea SI entrano
altri 4 bit che sovrascrivono il dato appena letto.
Questi problemi vengono facilmente eliminati attraverso il collegamento in parallelo dei
FF; la Figura 8.15 mostra appunto un cosiddetto registro parallelo costituito
anche in questo caso da 4 FF-D.
Come si può notare, ogni FF ha l'ingresso D e l'uscita Q indipendenti
da quelli degli altri FF; in fase di scrittura, i bit arrivano in parallelo (e tutti
nello stesso istante) sugli ingressi D, mentre in fase di lettura, i bit vengono letti
sempre in parallelo (e tutti nello stesso istante) dalle uscite Q.
Ripetendo l'esempio del registro a scorrimento, si vede che in fase di memorizzazione del
nibble 1010b, i 4 bit si presentano contemporaneamente sui 4 ingressi
che assumeranno quindi la configurazione D3=1, D2=0, D1=1, D0=0;
a questo punto basta inviare un solo ciclo di clock per copiare in un colpo solo i 4
bit sulle rispettive uscite.
Per leggere il dato appena memorizzato, basta leggere direttamente le 4 uscite
Q3, Q2, Q1, Q0; la lettura avviene senza distruggere il dato
stesso.
I registri paralleli risolvono il problema del tempo di accesso e della distruzione
dell'informazione in memoria in fase di lettura, ma presentano lo svantaggio di una maggiore
complessità circuitale; un registro parallelo a n bit richiederà, infatti, solo per
l'I/O dei dati, n linee di ingresso e n linee di uscita.
Per le loro caratteristiche i registri vengono largamente utilizzati quando si ha la
necessità di realizzare piccole memorie ad accesso rapidissimo; nel campo dei computer
sicuramente l'applicazione più importante dei registri è quella relativa alle memorie
interne delle CPU. Tutte le CPU sono dotate, infatti, di piccole memorie interne
attraverso le quali possono gestire le informazioni (dati e istruzioni) da elaborare; a tale
proposito si utilizzano proprio i registri paralleli che garantiscono le massime prestazioni
in termini di velocità di accesso.
8.4 Memorie RAM
L'acronimo RAM significa Random Access Memory (memoria ad accesso casuale);
come già sappiamo, il termine "casuale" non si riferisce al fatto che l'accesso a questo
tipo di memoria avviene a caso, bensì al fatto che il tempo necessario alla CPU
per accedere ad una cella qualunque (scelta a caso) è costante e quindi assolutamente
indipendente dalla posizione in memoria della cella stessa. Al contrario, sulle vecchie
unità di memorizzazione a nastro (cassette), il tempo di accesso dipendeva dalla posizione
del dato (memorie ad accesso sequenziale); più il dato si trovava in fondo, più aumentava
il tempo di accesso in quanto bisognava far scorrere un tratto di nastro sempre più lungo.
La RAM è accessibile, sia in lettura, sia in scrittura e il suo ruolo sul computer
è quello di memoria principale (centrale) ad accesso rapido; un programma per poter essere
eseguito deve essere prima caricato nella RAM, garantendo così alla CPU la
possibilità di accedere ai dati e alle istruzioni in tempi ridottissimi. In base a quanto
è stato esposto in precedenza, non appena si spegne il computer tutto il contenuto della
RAM viene perso; se si vogliono conservare in modo permanente programmi e altre
informazioni, bisogna procedere al loro salvataggio sulle memorie di massa.
8.4.1 Memorie RAM statiche (SRAM)
Nella sezione 8.1 di questo capitolo è stato detto che una memoria di lavoro è
organizzata sotto forma di matrice di celle; se le varie celle vengono realizzate con i
FF, otteniamo una memoria di lavoro organizzata sotto forma di matrice di registri.
Ogni registro deve essere formato naturalmente da un numero fisso di FF; nel caso,
ad esempio, delle architetture 80x86, ogni registro è formato da 8 FF.
Analizziamo ora a titolo di curiosità la struttura circuitale di una RAM; la Figura
8.16 illustra, ad esempio, una semplicissima RAM formata da 4 righe e una sola
colonna da 4 FF.
In questo caso essendo presente una sola colonna non abbiamo bisogno del circuito per
la decodifica colonna; è presente, invece, sulla sinistra il circuito per la
decodifica riga. Per poter selezionare una tra le 4 righe presenti abbiamo
bisogno di un Address Bus formato da log24=2 linee (A0
e A1); analizzando il circuito per la decodifica riga si può constatare
facilmente che, a seconda dei livelli logici presenti sulle due linee A0 e
A1, viene selezionata una sola tra le 4 righe presenti.
Supponiamo, ad esempio, di selezionare la riga 1 (A0=1, A1=0); in
questo caso possiamo osservare che solo sulla linea della riga 1 è presente
un livello logico 1, mentre sulle altre 3 linee di riga è presente un
livello logico 0. Se ora vogliamo leggere il nibble memorizzato nei 4
FF della riga 1, la logica di controllo pone CS=1, R/W=1,
OE=1; sulla linea CS transita il segnale Chip Select (selezione
circuito di memoria), sulla linea R/W transita il segnare Read/Write
(lettura/scrittura), mentre sulla linea OE transita il segnale Output
Enable (abilitazione lettura).
Se proviamo a seguire il percorso dei livelli logici che arrivano da CS, R/W
e OE possiamo constatare che agli ingressi CLK (clock) di tutti i FF
arriva un livello logico 0 (clock disabilitato); in questo caso come già sappiamo
i FF non eseguono alcuna operazione di memorizzazione. Siccome CS=1,
R/W=1 e OE=1, la porta AND più in basso nel circuito di Figura 8.16
produce un livello logico 1 che raggiunge il circuito per l'abilitazione della lettura;
in questo modo vengono abilitate le 4 linee del Data Bus indicate in Figura 8.16
con Data Output. Dalle 4 uscite Out0, Out1, Out2 e
Out3 vengono prelevati i 4 bit memorizzati nei 4 FF della riga
1; osserviamo che dalle altre 3 righe vengono letti 3 nibble che valgono
tutti 0000b e vengono poi combinati con il nibble della riga 1 attraverso le
porte OR a 4 ingressi. Supponiamo che la riga 1 memorizzi il nibble
1101b; in questo caso sulle 4 linee Data Output si ottiene proprio:
0000b OR 1101b OR 0000b OR 0000b = 1101b
A questo punto la logica di controllo pone CS=0 disabilitando il chip di memoria;
in questo modo si pone fine all'operazione di lettura.
Supponiamo ora di voler scrivere un nibble nei 4 FF della riga 1; in
questo caso la logica di controllo pone CS=1, R/W=0 e OE=0 disabilitando
così il circuito Data Output per la lettura. Se proviamo a seguire il percorso dei
livelli logici che arrivano da CS, R/W e OE possiamo constatare che agli
ingressi CLK (clock) dei 4 FF della riga 1 arriva un livello
logico 1; sugli ingressi CLK dei FF di tutte le altre righe arriva,
invece, un livello logico 0 (clock disabilitato). In un caso del genere come già
sappiamo ciascuno dei 4 FF della riga 1 copia sulla sua uscita Q
il segnale presente sul suo ingresso D (ricordiamo che con i registri paralleli
l'operazione di scrittura richiede un solo ciclo di clock); i 4 segnali che vengono
memorizzati sono proprio quelli che arrivano dalle 4 linee del Data Bus
indicate con In0, In1, In2 e In3 (Data Input).
A questo punto la logica di controllo pone CS=0 disabilitando il chip di memoria;
in questo modo si pone fine all'operazione di scrittura.
In base a quanto è stato appena esposto, possiamo dedurre una serie di considerazioni
relative alle memorie RAM realizzate con i FF; osserviamo innanzi tutto
che le informazioni memorizzate in una RAM di questo tipo, si conservano inalterate
senza la necessità di ulteriori interventi esterni. Si può dire che una RAM di
questo tipo conservi staticamente le informazioni finché permane l'alimentazione
elettrica; proprio per questo motivo, una memoria come quella appena descritta viene
definita Static RAM (RAM statica) o SRAM.
Sicuramente, il vantaggio fondamentale di una SRAM è rappresentato dai tempi di
accesso estremamente ridotti, mediamente dell'ordine di una decina di nanosecondi (1
nanosecondo = un miliardesimo di secondo = 10-9 secondi); questo aspetto
è fondamentale per le prestazioni del computer in quanto buona parte del lavoro svolto
dalla CPU consiste proprio nell'accedere alla memoria. Se la memoria non è in grado
di rispondere in tempi sufficientemente ridotti, la CPU è costretta a girare a vuoto
in attesa che si concludano le operazioni di I/O; in un caso del genere le prestazioni
generali del computer risulterebbero nettamente inferiori a quelle potenzialmente offerte
dal microprocessore.
Purtroppo questo importante vantaggio offerto dalle SRAM non riesce da solo a
bilanciare i notevoli svantaggi; osservando la struttura della SRAM di Figura 8.16 e
la struttura del FF di Figura 8.12, si nota in modo evidente l'enorme complessità
circuitale di questo tipo di memorie. Finché si ha la necessità di realizzare RAM
velocissime e di piccole dimensioni, la complessità circuitale non rappresenta certo un
problema; in questo caso la soluzione migliore consiste sicuramente nello sfruttare le
notevoli prestazioni velocistiche dei FF. Se però si ha la necessità di
realizzare memorie di lavoro di grosse dimensioni (decine o centinaia di MiB), sarebbe
teoricamente possibile utilizzare i FF, ma si andrebbe incontro a problemi talmente
gravi da rendere praticamente improponibile questa idea; si otterrebbero, infatti, circuiti
eccessivamente complessi, con centinaia di milioni di porte logiche, centinaia di milioni
di linee di collegamento, elevato ingombro, costo eccessivo, etc.
Proprio per queste ragioni, tutti i moderni PC sono dotati di memoria centrale di
tipo dinamico; come però viene spiegato più avanti, le RAM dinamiche pur
essendo nettamente più economiche e più miniaturizzabili delle SRAM, presentano
il grave difetto di un tempo di accesso sensibilmente più elevato. Con la comparsa sul
mercato di CPU sempre più potenti, questo difetto ha cominciato ad assumere un peso
del tutto inaccettabile; per risolvere questo problema, i progettisti dei PC hanno
deciso allora di ricorrere ad un espediente che permette di ottenere un notevole
miglioramento della situazione. A partire dall'80486 tutte le CPU della
famiglia 80x86 sono state dotate di una piccola SRAM interna chiamata
cache memory (memoria nascondiglio); il trucco che viene sfruttato consiste nel
fatto che, nell'eseguire un programma presente nella memoria centrale, la CPU
trasferisce nella cache memory le istruzioni che ricorrono più frequentemente.
Queste istruzioni vengono quindi eseguite in modo estremamente efficiente grazie al fatto
che la cache memory offre prestazioni nettamente superiori a quelle della memoria
centrale.
A causa dello spazio esiguo dovuto alle esigenze di miniaturizzazione, la cache memory
presente all'interno delle CPU è piuttosto limitata e ammonta mediamente a pochi
KiB; questa memoria viene chiamata L1 cache o first level cache (cache di
primo livello). Se si vuole ottenere un ulteriore miglioramento delle prestazioni, è
possibile dotare il computer di una cache memory esterna alla CPU che viene
chiamata L2 cache o second level cache (cache di secondo livello); tutti i
moderni PC incorporano al loro interno una L2 cache che mediamente ammonta a
qualche centinaio di KiB.
8.4.2 Memorie RAM dinamiche (DRAM)
La memoria centrale degli attuali PC ammonta ormai a diverse centinaia di MiB; le
notevoli dimensioni della memoria centrale devono però conciliarsi con il rispetto di
una serie di requisiti che riguardano, in particolare, la velocità di accesso, il costo di
produzione e il livello di miniaturizzazione. Come abbiamo appena visto, le SRAM
offrono prestazioni particolarmente elevate in termini di velocità di accesso, ma non
possono soddisfare gli altri requisiti; proprio per questo motivo, gli attuali PC
vengono equipaggiati con un tipo di memoria centrale chiamato Dynamic RAM o
DRAM (RAM dinamica).
In una DRAM vengono sfruttate le caratteristiche dei condensatori elettrici; si
definisce condensatore elettrico un sistema formato da due conduttori elettrici tra i
quali si trova interposto un materiale isolante. Ciascuno dei due conduttori elettrici
viene chiamato armatura; in Figura 8.17 vediamo, ad esempio, un condensatore
(C) formato da due armature piane tra le quali si trova interposta l'aria.
In Figura 8.17a notiamo che se l'interruttore T è aperto, non può circolare la
corrente elettrica e quindi il generatore non può caricare il condensatore; in queste
condizioni tra le due armature del condensatore si misura una tensione elettrica nulla
(0V).
In Figura 8.17b notiamo che se l'interruttore T viene chiuso, la corrente elettrica
può circolare e quindi l'energia elettrica erogata dal generatore comincia ad accumularsi
nel condensatore; alla fine del processo di carica, tra le due armature del condensatore
si misura una tensione elettrica pari a quella fornita dal generatore (+5V).
In Figura 8.17c notiamo che se l'interruttore T viene riaperto, il condensatore rimane
isolato dall'esterno e quindi teoricamente dovrebbe conservare il suo stato di carica; anche
in questo caso tra le due armature del condensatore si misura una tensione elettrica di
+5V. Se vogliamo scaricare il condensatore, dobbiamo collegarlo, ad esempio, a una
resistenza elettrica; in questo modo la resistenza assorbe l'energia elettrica dal
condensatore e la converte in energia termica.
Dalle considerazioni appena esposte si deduce immediatamente che anche il condensatore
rappresenta una semplicissima unità elementare di memoria capace di gestire un singolo
bit; possiamo associare, infatti, il livello logico 0 al condensatore scarico
(0V) e il livello logico 1 al condensatore carico (+Vcc). Mettendo
in pratica questi concetti si perviene al circuito di Figura 8.18 che illustra in linea di
principio la struttura di una unità elementare di memoria di tipo DRAM.
Come si può notare, l'unità elementare di memoria è composta da un transistor T
di tipo NMOS e da un condensatore C; il confronto tra la Figura 8.18 e la
Figura 8.12 evidenzia la notevole semplicità di una unità DRAM rispetto a una
unità SRAM. La linea indicata con D rappresenta la linea dati
utilizzata per le operazioni di I/O, mentre la linea RG serve per abilitare
o disabilitare la riga su cui si trova la cella desiderata; come al solito si assume che
il potenziale di massa (GND) sia pari a 0V.
Se la linea RG si trova a livello logico 0, si vede chiaramente in Figura 8.18
che il transistor T si porta allo stato OFF (interdizione); in queste
condizioni il condensatore C risulta isolato dall'esterno per cui teoricamente
dovrebbe conservare indefinitamente il bit che sta memorizzando. Come è stato detto in
precedenza, se il condensatore è scarico sta memorizzando un bit di valore 0; se,
invece, il condensatore è carico sta memorizzando un bit di valore 1.
Per effettuare una operazione di lettura o di scrittura bisogna portare innanzi tutto la
linea RG a livello logico 1 in modo che il transistor T venga portato
allo stato ON (saturazione); a questo punto il condensatore può comunicare
direttamente con la linea D per le operazioni di I/O.
L'operazione di scrittura consiste semplicemente nell'inviare un livello logico 0 o
1 sulla linea D; ovviamente questo livello logico rappresenta il valore del
bit che vogliamo memorizzare. Ponendo D=0, il condensatore si ritrova con entrambe
le armature a potenziale 0V; in queste condizioni il condensatore si scarica (se non
era già scarico) e memorizza quindi un bit di valore 0. Ponendo, invece, D=1,
il condensatore si ritrova con una armatura a potenziale +Vcc e l'altra a potenziale
di massa 0V; in queste condizioni il condensatore si carica (se non era già carico)
e memorizza quindi un bit di valore 1.
L'operazione di lettura con l'unità DRAM di Figura 8.18 consiste semplicemente nel
misurare il potenziale della linea D; se il potenziale è 0V (condensatore
scarico) si ottiene la lettura D=0, mentre se il potenziale è +Vcc (condensatore
carico) si ottiene la lettura D=1.
Come è stato già evidenziato, l'unità elementare DRAM risulta notevolmente più
semplice dell'unità elementare SRAM rendendo quindi possibile la realizzazione di
memorie di lavoro di notevoli dimensioni, caratterizzate da una elevatissima densità di
integrazione e da un costo di produzione estremamente ridotto; per quanto riguarda
l'organizzazione interna, le DRAM vengono strutturate sotto forma di matrice
rettangolare di celle esattamente come avviene per le SRAM.
Dalle considerazioni appena esposte sembrerebbe che le DRAM siano in grado di
risolvere tutti i problemi presentati dalle SRAM, ma purtroppo bisogna fare i conti
con un aspetto piuttosto grave che riguarda i condensatori; in precedenza è stato detto
che un condensatore carico isolato dal mondo esterno conserva indefinitamente il suo stato
di carica. Un condensatore di questo genere viene chiamato ideale ed ha un significato
puramente teorico; in realtà accade che a causa degli inevitabili fenomeni di dispersione
elettrica, un condensatore come quello di Figura 8.18 si scarica nel giro di poche decine di
millisecondi. Per evitare questo problema tutte le memorie DRAM necessitano di un
apposito dispositivo esterno che provvede ad effettuare periodicamente una operazione di
rigenerazione (refreshing) delle celle; nelle moderne DRAM questo dispositivo
viene integrato direttamente nel chip di memoria.
Al problema del refreshing bisogna aggiungere il fatto che le operazioni di carica e scarica
dei condensatori richiedono tempi relativamente elevati; la conseguenza negativa di tutto
ciò è data dal fatto che le DRAM presentano mediamente tempi di accesso pari a
60, 70 nanosecondi. Una CPU come l'80486 DX a 33 MHz
è in grado di effettuare una operazione di I/O in memoria in appena 1 ciclo
di clock, pari a:
1 / 33000000 = 0.00000003 secondi = 30 nanosecondi
Questo intervallo di tempo è nettamente inferiore a quello richiesto da una DRAM da
60 nanosecondi; per risolvere questo problema, i progettisti dei PC hanno
escogitato i cosiddetti wait states (stati di attesa). In pratica, osservando che
60 nanosecondi sono il doppio di 30 nanosecondi, la CPU predispone la
fase di accesso in memoria in un ciclo di clock, dopo di che gira a vuoto per un altro ciclo
di clock dando il tempo alla memoria di rispondere; questa soluzione appare piuttosto
discutibile visto che, considerando il fatto che la CPU dedica buona parte del suo
tempo agli accessi in memoria, ci si ritrova con un computer che lavora prevalentemente a
velocità dimezzata (nel caso dell'80486 si ha, infatti: 33/2=16.5 MHz).
Come è stato detto in precedenza, questo ulteriore problema viene parzialmente risolto
attraverso l'uso della cache memory di tipo SRAM; l'idea di fondo consiste
nel creare nella cache una copia delle informazioni che si trovano nella DRAM
e che vengono accedute più frequentemente dalla CPU. La cache ha la precedenza
sulla RAM per cui, ogni volta che la CPU deve accedere ad una informazione, se
quell'informazione è presente nella cache l'accesso avviene in un solo ciclo di clock; in
caso contrario, l'informazione viene letta dalla DRAM con conseguente ricorso agli
stati di attesa.
8.4.3 le nuove memorie Double Data Rate (DDRx)
Nei computer moderni è stato introdotto il nuovo standard di memoria DDR (Double Data Rate),
che introduce la possibilità di leggere i dati sul doppio fronte di clock, trasmettendoli
sia sul fronte di salita che su quello di discesa; è possibile quindi raddoppiare
la velocità di trasferimento dei dati verso la CPU senza per questo aumentare
né la frequenza del clock interno alla memoria, né quella del bus dimezzando i tempi di attesa della CPU,
questo sistema è molto vantaggioso rispetto alle DRAM (Dynamic Random Access Memory)
che inviavano i dati verso la CPU solo sul fronte di salita e per questo avevano un tempo di latenza doppio.
A questo proposito viene introdotta l’unità di misura
MT/s, un (megatransfer) rappresenta esattamente un milione di trasferimenti di dati, indipendentemente
dalla frequenza di clock sottostante.
Questo rende i MT/s una misura più accurata e diretta delle effettive capacità di trasferimento
dei dati della memoria. Per esempio, una Ram (Ddr4-3200) opera con una frequenza di base di
1600 Mhz, ma può effettuare 3200 MT/s grazie alla tecnologia Double Data Rate.
Poiché i dati sono trasferiti in pacchetti di 8 byte per volta (il bus è sempre a 64 bit) una RAM
DDR consente una velocità teorica di trasferimento con una frequenza di clock di 100
MHz, con 2 trasferimenti per ogni ciclo di clock e un pacchetto di 8 byte ad ogni trasferimento, otteniamo così una banda di
trasferimento dati da 1600 Mbit/s.
100 * (2 * 8) = 1600Mbit/s
l’unità di misura Mbps (megabit per secondo) tiene conto non solo del numero di
trasferimenti, ma anche della larghezza del bus della memoria. Nel caso della Ram di un sistema moderno,
che utilizza un bus a 64 bit, ogni trasferimento muove appunto 64 bit di dati. Quindi, una
Ram che opera a 3200 MT/s su un bus a 64 bit avrà una larghezza di banda
massima teorica di 25600 Mbit/s
3200MT/s * 64bit = 204800bit/s / 8byte = 25600Mbit/s
Nella realtà la situazione è più complessa, poiché la velocità di trasferimento
è notevolmente influenzata dai fenomeni di latenza, che si verificano durante le operazioni di lettura/scrittura
e che dipendono strettamente dal tipo e dalla qualità del chip, nonché dalla frequenza di funzionamento.
Per quantificare tali fenomeni, ad ogni banco di memoria vengono associati dei tempi caratteristici detti timing, misurati a parità di frequenza
in unità di cicli per clock.
I timing della memoria sono espressi tipicamente attraverso quattro numeri (per esempio, 16-18-18-36);
questi numeri indicano rispettivamente tCL (la latenza CAS), tRCD (il tempo necessario tra l’attivazione di una riga
e l’accesso a una colonna), tRP (il tempo richiesto per disattivare una riga prima di attivarne un’altra)
e tRAS (Il tempo minimo che una riga deve rimanere attiva per garantire l’accesso ai dati).
8.5 Memorie ROM
L'acronimo ROM significa Read Only Memory (memoria a sola lettura); come dice
il nome, queste memorie sono accessibili solo in lettura in quanto il loro scopo è quello
di conservare dati e istruzioni la cui modifica potrebbe compromettere il funzionamento del
computer.
Il contenuto di queste memorie viene spesso impostato in fase di fabbricazione e permane
anche in assenza di alimentazione elettrica; tutto ciò potrebbe sembrare impossibile visto
che anche le ROM sono memorie elettroniche che per funzionare hanno bisogno di essere
alimentate elettricamente. La conservazione delle informazioni all'interno di una ROM
viene ottenuta attraverso svariati metodi; uno di questi metodi viene illustrato dalla
Figura 8.19 che mostra una semplicissima ROM formata da 4 transistor di tipo
BJT.
La ROM di Figura 8.19 è formata da due linee di riga e due linee di colonna; le due
righe sono collegate alle linee A0 e A1 dell'Address Bus, mentre le
due colonne forniscono in uscita i livelli logici Out0 e Out1 che, come
vedremo in seguito, sono una logica conseguenza dei valori assunti da A0 e
A1.
Ricordando quanto è stato detto nel Capitolo 5 sul BJT, se sulla base arriva un
livello logico 0, il transistor si porta in interdizione (OFF); il potenziale
+Vcc (livello logico 1) presente sul collettore non può trasferirsi
sull'emettitore e quindi sull'uscita di emettitore avremo un livello logico 0.
Se, invece, sulla base arriva un livello logico 1, il transistor si porta in saturazione
(ON); il potenziale +Vcc (livello logico 1) presente sul collettore si
trasferisce sull'emettitore e quindi sull'uscita di emettitore avremo un livello logico
1.
In Figura 8.19 si nota anche la presenza di un transistor con l'emettitore non collegato
alla linea di colonna; questi transistor forniscono alla corrispondente colonna sempre
un livello logico 0 e quindi memorizzano di fatto un bit di valore 0.
A questo punto possiamo capire il principio di funzionamento della memoria ROM di
Figura 8.19; i livelli logici assunti dai due ingressi A0 e A1 determinano in
modo univoco i livelli logici ottenibili sulle uscite Out0 e Out1. Inviando,
ad esempio, sull'Address Bus l'indirizzo 10b (cioè A0=0 e A1=1),
si ottiene in uscita Out0=1 e Out1=0; questi due livelli logici che si
ottengono in uscita sono legati univocamente all'indirizzo in ingresso 10b.
Possiamo dire quindi che in realtà la memoria ROM mostrata in Figura 8.19 non contiene
alcuna informazione al suo interno; queste informazioni si vengono a creare automaticamente
nel momento in cui la ROM viene alimentata e indirizzata.
La memoria appena descritta rappresenta una ROM propriamente detta; la struttura
interna delle ROM propriamente dette viene impostata una volta per tutte in fase
di fabbricazione e non può più essere modificata. Come abbiamo visto nel precedente
capitolo, sui PC le ROM vengono utilizzate per memorizzare dati e programmi
di vitale importanza per il corretto funzionamento del computer; in particolare, si possono
citare le procedure del BIOS, il programma per il POST e il programma per il
bootstrap.
Oltre alle ROM propriamente dette, esistono anche diverse categorie di ROM
che possono essere programmate; analizziamo brevemente i principali tipi di ROM
programmabili:
PROM o Programmable ROM (ROM programmabili).
Le PROM sono molto simili alle ROM propriamente dette, ma si differenziano
per il fatto di contenere al loro interno dei microfusibili (uno per ogni transistor)
posti tra l'uscita di emettitore e la corrispondente colonna; attraverso una apposita
apparecchiatura è possibile far bruciare alcuni di questi microfusibili in modo da
impostare la struttura interna della PROM a seconda delle proprie esigenze. I
microfusibili vengono bruciati (e quindi interrotti) attraverso una corrente pulsante di
circa 100 mA; da queste considerazioni risulta chiaramente il fatto che le
PROM possono essere programmate una sola volta.
EPROM o Erasable Programmable ROM (ROM programmabili e cancellabili).
Nelle EPROM i BJT vengono sostituiti da transistor MOS che contengono
al loro interno anche un secondo gate chiamato gate fluttuante; questo secondo gate
viene immerso in uno strato di biossido di silicio (SiO2) e quindi si
trova ad essere elettricamente isolato. Attraverso appositi impulsi di tensione applicati
tra drain e source, è possibile determinare per via elettrostatica un accumulo di carica
elettrica nel gate fluttuante; la carica così accumulata è isolata dall'esterno e quindi
può conservarsi per decine di anni. I gate fluttuanti elettricamente carichi rappresentano
un bit di valore 1, mentre quelli scarichi rappresentano un bit di valore 0.
Il contenuto delle EPROM può essere cancellato attraverso esposizione ai raggi
ultravioletti; per questo motivo, i contenitori che racchiudono le EPROM sono
dotati di apposite finestre trasparenti che favoriscono il passaggio degli ultravioletti.
Una volta che la EPROM è stata cancellata può essere riprogrammata con la tecnica
descritta in precedenza; generalmente sono possibili una cinquantina di riprogrammazioni.
EAROM o Electrically Alterable ROM (ROM alterabili elettricamente).
L'inconveniente principale delle EPROM descritte in precedenza è dato dal fatto
che queste memorie per poter essere cancellate devono essere rimosse dal circuito in cui
si trovano inserite; per ovviare a questo inconveniente sono state create appunto le
EAROM. Le EAROM sono del tutto simili alle EPROM, ma si differenziano
per la presenza di appositi circuiti che attraverso l'utilizzo di impulsi elettrici
permettono, sia la programmazione, sia la cancellazione della memoria; in sostanza, le
EAROM possono essere considerate delle vere e proprie RAM non volatili,
cioè delle RAM che conservano il loro contenuto anche in assenza di alimentazione
elettrica.
L'evoluzione delle EAROM ha portato alla nascita delle EEPROM chiamate
anche E2PROM (Electrically Erasable PROM); nelle EEPROM
lo strato isolante attorno al gate fluttuante viene ridotto a pochi centesimi di micron
facilitando in questo modo le operazioni di scrittura e di cancellazione.
NOVRAM o Non Volatile RAM (RAM non volatili).
Le NOVRAM possono essere definite come le ROM dell'ultima generazione;
queste memorie vengono ottenute associando una RAM con una EEPROM aventi
la stessa capacità di memorizzazione in byte. In fase operativa, tutte le operazioni
di I/O vengono effettuate ad alta velocità sulla RAM; nel momento in cui
si vuole spegnere l'apparecchiatura su cui è installata la NOVRAM, tutto il
contenuto della RAM viene copiato in modo permanente nella EEPROM.