Calcolatori Elettronici - Libero Community Siti Personali

A p p u n t i d i C a lco la t o ri Elet t ro n ici
C a p it o lo 5
T ecn i ch e p er l a real i zzazi on e d ei p roces s ori
Introduzione .................................................................................................................. 2
Richiami sulla macchina di von Neumann ........................................................................ 2
Unità di calcolo del processore .................................................................................... 2
Tipica struttura di un processore: il DLX ......................................................................... 3
Fasi fondamentali dell’esecuzione di una istruzione.......................................................... 6
Latenza delle istruzioni ............................................................................................. 12
I L “ CONTROLLO ”.............................................................................................................. 14
Introduzione ................................................................................................................ 14
Diagramma a stati finiti descrittivo del controllo ........................................................... 14
Controllo dedicato (nel DLX) ........................................................................................ 16
Riduzione dei costi dell’hardware del controllo dedicato.............................................. 17
Prestazioni del controllo dedicato............................................................................... 17
Controllo microprogrammato ........................................................................................ 18
Le microistruzioni..................................................................................................... 19
Fondamenti della microprogrammazione ..................................................................... 20
Esecuzione di una istruzione................................................................................... 21
Esempio: microprogramma per la fase di fetch di una istruzione................................ 23
Riduzione dei costi dell’hardware .............................................................................. 24
Codifica delle linee di controllo .............................................................................. 24
Formati multipli di microistruzioni ......................................................................... 25
Aggiunta di controllo dedicato per la condivisione di microcodice............................ 26
Riduzione del CPI ..................................................................................................... 27
Uso di microcodice specializzato ............................................................................ 27
Aggiunta di controllo dedicato ................................................................................ 27
Uso del “parallelismo” ........................................................................................... 28
Questione degli accessi incompleti alla memoria ............................................................ 28
ARCHITETTURA AD ACCUMULATORE .................................................................................. 29
Descrizione dell’architettura ........................................................................................ 29
Modalità di indirizzamento e loro implementazione ........................................................ 30
Istruzioni di salto ......................................................................................................... 33
Caso particolare: salto da interruzione ........................................................................ 35
L E INTERRUZIONI ............................................................................................................. 37
Introduzione ................................................................................................................ 37
Terminologia delle interruzioni .................................................................................. 38
Concetti generali sulle interruzioni ............................................................................... 38
Classificazione degli eventi che determinano interruzioni ............................................... 39
Rilevazione delle interruzioni da parte del controllo ....................................................... 41
Il controllo per il DLX .................................................................................................. 44
Appunti di “Calcolatori Elettronici” – Capitolo 5
Introduzione
Il termine “architettura”, in campo edilizio, definisce sostanzialmente quale forma
debba avere un edificio; ad essa si affianca la “carpenteria”, che invece determina la
qualità della costruzione. L’equivalente della carpenteria, nell’ambito dei calcolatori
elettronici, è la realizzazione, il cui ruolo è quello di stabilire due fondamentali
parametri per le prestazioni: il numero di cicli di clock per istruzione (brevemente CPI)
e la durata del singolo ciclo di clock (detta anche periodo di clock).
In questo capitolo vogliamo esporre i concetti fondamentali della progettazione dei
processori, concentrandoci in particolar modo sul controllo di flusso e sulle
interruzioni.
Richiami sulla macchina di von Neumann
Parecchi anni fa, il generico calcolatore elettronico fu suddiviso da von Neumann
in un certo numero di componenti di base, che esistono ancora oggi, nonostante
molte cose siano cambiate da allora:
Unità Centrale di Elaborazione
(CPU o processore)
Unità di
controllo
Unità di
calcolo
registri
Memoria
centrale
Stampante
Disco
BUS
Organizzazione di un semplice calcolatore con una CPU e due dispositivi di I/O
La CPU, che al giorno d’oggi è diventato sinonimo di processore, è il nucleo del
calcolatore: essa contiene tutto tranne la memoria e le funzioni di ingresso (Input) e
di uscita (Output). A sua volta, il processore comprende fondamentalmente due
elementi: unità di controllo e unità di calcolo.
U
Un
niittàà d
dii ccaallccoolloo d
deell p
prroocceessssoorree
Oggi, l’organo “aritmetico” definito da von Neumann prende il nome di unità di
calcolo ed è costituito da tre “tipi” di elementi:
•
due o più unità di esecuzione, come l’unità aritmetico-logica (ALU) e l’unità
di scorrimento;
•
registri;
Autore: Sandro Petrizzelli
2
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
•
collegamenti tra i registri.
Dal punto di vista del programmatore, l’unità di calcolo contiene la maggior parte
dello stato del processore: tale “stato” corrisponde alle informazioni che devono
essere salvate qualora si voglia sospendere e poi riattivare l’esecuzione di un
programma.
Oltre ai registri di tipo generale, che sono visibili all’utente, l’unità di calcolo
comprende almeno altri tre particolari registri, le cui funzioni saranno descritte più
avanti:
•
il registro contatore di programma (PC, program counter);
•
il registro di indirizzo delle interruzioni (IAR, Interrupt Address Register);
•
il registro di stato (detto talvolta PSW, cioè Program Status Word)
In particolare, il registro di stato contiene tutti gli indicatori di stato (flag) della
macchina: ad esempio, ci sono l’indicatore di abilitazione delle interruzioni, gli
indicatori di condizione e così via.
Dato che la tecnologia adottata determina la realizzazione del calcolatore, questa a
sua volta determina la durata del ciclo di clock (periodo di clock). Tale durata è
chiaramente determinata dai circuiti più lenti: generalmente, è proprio l’unità di
calcolo ad essere più lenta degli altri circuiti del processore.
La stessa l’unità di calcolo determina in modo preponderante il costo del
processore, visto che solitamente comprende la metà del transistor del processore e
occupa praticamente la metà della sua superficie di silicio. Nonostante l’importanza
rivestita dall’unità di calcolo, essa risulta comunque il “componente” più semplice da
progettare.
Tipica struttura di un processore: il DLX
E’ opportuno esaminare una schematizzazione della struttura del processore che
useremo in seguito per i nostri discorsi, riportata nella prossima figura (pag. 4).
La prima cosa che si nota nella figura è la suddivisione del processore in unità di
controllo (a sinistra) ed unità di calcolo; quest’ultima è a sua volta suddivisa in
una serie di componenti che descriveremo tra un attimo e costituisce la parte
preponderante del processore.
Nella parte inferiore della figura viene anche schematizzato il collegamento del
processore con la memoria, tramite tre “linee”: due linee (collegate ai registri IR ed
MDR) servono per il trasferimento dei dati (sia in ingresso sia in uscita) e delle
istruzione (solo in uscita) in memoria, mentre la terza linea (collegata al registro
MAR) serve al processore per specificare l’indirizzo di memoria coinvolto nelle
operazioni di trasferimento.
Notiamo inoltre che i collegamenti dell’unità di controllo sono tratteggiati, mentre
quelli dell’unità di calcolo sono continui: il motivo è nel fatto che l’unità di controllo,
tramite appositi segnali, esercita una sorta di “vigilanza” (o, appunto, “controllo”) su
quello che avviene nell’unità di calcolo, la quale invece prevede collegamenti per
l’interscambio di segnali corrispondenti a dati veri e propri.
aggiornamento: 10 luglio 2001
3
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Processore
Unità di calcolo
S1
Bus
Dest
Bus
S2
Bus
ALU
A
B
Banco
dei
Registri
Unità di
controllo
C
Temp
PC
IAR
MAR
IR
MDR
MUX
Ingresso dato
Memoria
Indirizzo
Uscita dato
Processore tipico, ripartito in unità di controllo ed unità di calcolo, più la memoria
Venendo alla struttura specifica dell’unità di calcolo, possiamo osservare quanto
segue:
1
•
il processore usa tre distinti bus, denominati S1, S2 e Dest. Il bus Dest ha il
compito di prelevare i valori prodotti in uscita dalla ALU e di collocarli o nel
banco di registri (passando per l’elemento C) oppure nei registri dedicati o
anche ad uno dei due ingressi del multiplexer (MXU); gli altri due bus, invece,
portano gli operandi in ingresso alla ALU, prelevandoli dai registri dedicati
oppure dal banco dei registri (in questo caso tramite gli elementi A e B);
•
la funzione fondamentale dell’unità di controllo è la lettura degli operandi dal
banco dei registri, la loro “trasformazione” nell’ALU e la rimemorizzazione del
risultato; dato che il banco di registri non viene letto e scritto in ogni ciclo di
clock, la maggior parte dei progettisti è convinta di dover accelerare queste
operazioni, per cui spesso si suddivide la sequenza in cicli di clock multipli e
si diminuisce al contempo la durata di ciascun ciclo ( 1). Per questo motivo,
nell’unità di calcolo compaiono degli “elementi di memoria” alle due uscite del
Questo concetto sarà più chiaro quando parleremo di pipelining
Autore: Sandro Petrizzelli
4
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
banco di registri (A e B) ed all’ingresso dello stesso banco (C): si tratta in
particolare di latch, che hanno lo scopo di conservare i dati per il tempo
necessario affinché la ALU possa svolgere i propri compiti;
•
il banco di registri contiene i 32 registri di tipo generale (GPR) del DLX;
•
il registro contatore di programma (PC) ed il registro di indirizzamento delle
interruzioni (IAR) fanno parte dello stato della macchina: il primo contiene
l’indirizzo della prossima istruzione da eseguire, mentre il secondo viene usato
per la gestione delle interruzioni (interrupt), in quanto contiene l’indirizzo di
ritorno dalle routine di interrupt ( 2);
•
sono poi previsti dei registri che invece non fanno parte dello stato della
macchina, ma servono per l’esecuzione delle istruzioni:
•
o
il registro di indirizzamento della memoria (MAR) contiene l’indirizzo di
memoria cui accedere per la lettura o la scrittura di dati;
o
il registro di lettura e scrittura della memoria (MDR) contiene i dati da
leggere o scrivere in memoria (all’indirizzo contenuto in MAR);
o
il registro delle istruzioni (IR) contiene sempre la prossima istruzione da
eseguire; esso presenta un collegamento diretto con la memoria ( 3), in
quanto riceve da essa le istruzioni; si comporta quindi in modo simile al
registro MDR, con la differenza che contiene solo istruzioni (mentre l’
MDR contiene solo dati) e viene usato solo per “leggere” dalla memoria
(mentre l’ MDR anche per “scrivere”);
o
il registro temporaneo (Temp) è un “registro a perdere”, nel senso che è
disponibile per la memorizzazione dei dati intermedi durante l’esecuzione
di alcune istruzioni del DLX;
infine, due cose molto importanti da notare sono le seguenti:
o
in primo luogo, l’unica via di comunicazione tra i bus S1 ed S2 ed il bus
Dest passa attraverso la ALU: questo significa, come avremo modo di dire
in seguito, che il passaggio di un dato da S1 (o S2) a Dest richiede
comunque il “funzionamento” della ALU, la quale naturalmente dovrà
lasciar passare inalterato il dato dall’ingresso all’uscita; si dice perciò, in
questi casi, che la ALU compie una operazione fittizia, nel senso
appunto che il dato in ingresso si ritrova invariato in uscita dopo un
certo tempo (necessario alla propagazione dei segnali nei circuiti
combinatori che compongono la ALU stessa);
o
in secondo luogo, si nota la mancanza di un registro Stack Pointer
(puntatore allo stack), che molte architetture usano per gestire lo stack
(usato, per esempio, per le chiamate a procedure ed i ritorni da
procedura): il motivo è che, essendo il DLX una architettura a registri
generali, è possibile usare uno qualsiasi dei registri del “banco di
registri” per tale funzione. Non solo, ma usando più registri si possono
anche realizzare più stack contemporaneamente.
2
Di questo argomento parleremo ampiamente più avanti in questo capitolo.
Quando parliamo di “collegamento diretto” tra un qualsiasi registro interno alla CPU e la memoria, ci riferiamo
implicitamente ad un collegamento del registro al bus. In particolare, il registro IR, come anche l’MDR, è collegato al bus
dati, mentre invece il registro MAR al bus indirizzi, per ovvi motivi.
3
aggiornamento: 10 luglio 2001
5
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Fasi fondamentali dell’esecuzione di una istruzione
Prima ancora di discutere il modo con cui viene effettuato il controllo di flusso nel
DLX, è opportuno analizzare con sufficiente dettaglio le fasi di esecuzione delle
istruzioni. A tal proposito, se escludiamo le istruzioni per la virgola mobile,
l’esecuzione di tutte le istruzioni del DLX può essere suddivisa in 5 fasi
fondamentali:
1. prelievo dell’istruzione dalla memoria (IF);
2. decodifica dell’istruzione (ID);
3. esecuzione dell’istruzione (EX);
4. accesso alla memoria (MEM);
5. scrittura del risultato (WB).
Ogni fase può impiegare uno o più cicli di clock, con riferimento ovviamente
all’architettura del DLX proposta in precedenza: per semplicità, escludendo le
istruzioni per la virgola mobile, supporremo d’ora in avanti che ogni fase richieda
esattamente un ciclo di clock ( 4).
Vediamo allora di descrivere le singole fasi:
1. fase di prelievo dell’istruzione dalla memoria (fase IF)
Usando la notazione simbolica che ci siamo abituati ad usare dal
precedente capitolo, possiamo descrivere questa fase mediante due semplici
"operazioni elementari":
MAR←PC;
IR←M[MAR]
La prima operazione consiste dunque nel caricare il registro MAR (che
contiene sempre l’indirizzo della parola di memoria cui si vuole accedere, in
lettura o scrittura) con il contenuto del registro PC, ossia con l’indirizzo
dell’istruzione da prelevare e poi eseguire; tale indirizzo viene dunque usato
per indirizzare la memoria e caricare l’istruzione nel registro IR. E’
importante notare che il contenuto del registro PC deve essere caricato nel
registro MAR, in quanto, come si vede dall’architettura del DLX, solo il MAR
è “collegato” alla memoria, mentre invece il PC no;
2. fase di decodifica dell’istruzione e di preparazione degli operandi (fase
ID)
Questa fase prevede che l’istruzione venga decodificata (tramite il suo
codice operativo) e che si acceda al banco di registri per leggere gli operandi
coinvolti nell’istruzione. Anche in questo caso, bastano poche istruzioni
simboliche per descrivere le operazioni da compiere:
4
Questa ipotesi parte in effetti da altre ipotesi a monte: ad esempio, vedremo più avanti che, in presenza di una memoria
cache interposta tra processore e memoria centrale, la lettura di un valore dalla memoria richiede un ciclo di clock solo se
il dato si trova nella memoria cache, mentre i cicli di clock richiesti aumentano se il dato deve essere preso dalla memoria
centrale.
Autore: Sandro Petrizzelli
6
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
A←Rs1;
B←Rs2
PC←PC+4
Da notare l’ultima istruzione, che provvede all’incremento del PC di 4 byte
(dato che tutte le istruzioni del DLX hanno lunghezza di 4 byte). Inoltre, con
Rs1 ed Rs2 sono stati indicati due qualsiasi registri del banco di registri a
disposizione del DLX.
E’ importante notare che la decodifica dell’istruzione e le tre operazioni
descritte poco fa avvengono qualunque sia il tipo di istruzione in questione:
questo è possibile proprio perché gli identificatori dei registri hanno sempre
la stessa posizione in tutte le istruzioni, in modo che la lettura avvenga
senza ambiguità (questa tecnica è nota come decodifica a campi fissi).
Naturalmente, la lettura potrebbe anche risultare inutile dopo che è stata
completata la decodifica, ma, non essendo distruttiva né richiedendo tempo
addizionale rispetto a quello della decodifica, vale comunque la pena di
effettuarla. Si tratta dunque di un primo banale metodo di ottimizzazione del
ciclo di esecuzione.
Affinché la lettura dei registri Rs1 ed Rs2 avvenga in parallelo con la
decodifica dell’istruzione, i valori dei due registri vengono inviati ai latch di
uscita A e B prima che l’istruzione venga decodificata. Non solo, ma, dato
che anche la parte dell’istruzione contenente un valore immediato è sempre
la stessa in ogni formato di istruzione del DLX, l’estensione in segno del
valore immediato si può calcolare anch’essa durante questa fase, a
prescindere dal fatto che venga richiesta nella fase successiva oppure no.
In generale, dunque, si applica il concetto per cui, nonostante alcune
operazioni dipendano dall’esito della decodifica, è comunque opportuno
effettuarle in parallelo alla decodifica stessa, in quanto questa scelta non ha
alcuna controindicazione e può consentire, qualora queste operazioni
risultino effettivamente necessarie, di migliorare le prestazioni;
3. fase di esecuzione e di indirizzamento (fase EX)
In questa fase (la più critica e spesso più lunga dell’intero processo), risulta
sempre coinvolta l’ALU, la quale elabora gli operandi predisposti nella fase
precedente ed esegue una tra le seguenti tre funzioni, a seconda del tipo di
istruzione:
•
riferimento alla memoria ( 5);
•
funzione dell’ALU;
•
diramazione/salto.
Esaminiamo ciascuna di tali funzioni:
5
Ricordiamo che il DLX è una architettura a registri generali di tipo load/store, il che significa che le uniche istruzioni di
accesso alla memoria sono quelle per il caricamento dei dati nei registri (load) e per la memorizzazione dei dati in
memoria (store). Non sono invece ammesse operazioni ALU direttamente su operandi in memoria, ma solo operazioni su
operandi contenuti nei registri.
aggiornamento: 10 luglio 2001
7
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Riferimento alla memoria (load/store):
MAR ← A + (IR 16 ) 16 ##IR 16..31 ;
MDR ← Rd
In questo caso, l’istruzione da eseguire è in formato I, per cui presenta un
registro base (Rs1,) precedentemente letto e caricato nel latch A, un registro
destinazione (Rd), usato solo nel caso di memorizzazione, ed un operando
immediato corrispondente ad uno spiazzamento da sommare al registro base;
quest’ultimo occupa gli ultimi 16 bit dell’istruzione, ossia quindi gli ultimi
16 bit del registro IR. Allora, il comportamento del processore è il seguente:
nell’arco di un unico colpo di clock, estende in segno ( 6) l’operando
immediato situato negli ultimi 16 bit del registro IR, lo somma all’operando
precedentemente reso disponibile ( in modo da ottenere l’indirizzo effettivo),
quindi carica tale indirizzo nel registro MAR ( 7).
A questo punto, il processore si potrebbe anche fermare, ma in realtà è
opportuna una ulteriore ottimizzazione: nel caso in cui l’istruzione in corso
di esecuzione sia una memorizzazione (cioè il trasferimento di un dato da un
registro destinazione Rd verso la memoria), risulta utile inizializzare sin da
ora il registro MDR con il dato contenuto in Rd. Nel caso, invece, l’istruzione
preveda un caricamento (cioè dalla memoria a MDR e da qui in Rd), allora
l’ottimizzazione sarebbe inutile ma comunque a costo zero.
Funzione dell’ALU:
UscitaALU ← A op (B oppure (IR 16 ) 16 ##IR 16..31 );
In questo caso, l’istruzione da eseguire richiede un calcolo (simboleggiato
dall’operatore generico op) specificato dal codice operativo; tale calcolo può
coinvolgere o meno un operando immediato: se è così , esso occupa gli ultimi
16 bit dell’istruzione, ossia quindi gli ultimi 16 bit del registro IR, mentre
invece, in caso contrario, si trovava nel banco di registri ed è stato reso
disponibile nel latch B così come l’altro operando nel latch A. Quindi, il
processore esegue l’operazione specificata (usando appunto, come secondo
operando, o il contenuto di B oppure l’estensione in segno del registro IR) e
memorizza il risultato in C (uscita dell’ALU).
6
L’estensione in segno è notoriamente una operazione che aumenta la dimensione (in termini di numero di bit) di un
numero, senza modificarne il valore: nel caso di numeri segnati, con quelli negativi espressi in complemento a 2,
l’estensione prevede che a sinistra del numero vengano introdotti un certo numero di bit uguali al bit più significativo del
numero originale; tale bit vale evidentemente 0 per i numeri positivi ed 1 per i numeri negativi. Ad esempio, dato il
numero positivo 00011101, la sua estensione in segno a 16 bit è 0000000000011101. Se invece il numero fosse 10011101
(negativo), la sua estensione in segno a 16 bit sarebbe 1111111110011101.
7
Segnaliamo che la procedura di “estensione in segno” del contenuto del registro IR viene effettuata da una apposita
circuiteria (logica dedicata) di tipo combinatorio, posta in uscita al registro IR.
Autore: Sandro Petrizzelli
8
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Diramazione/Salto:
Diramazione:
UscitaALU ← PC + (IR 16 ) 16 ##IR 16..31 ;
cond ← (A op 0)
Salto:
UscitaALU ← PC + (IR 26 ) 6 ##IR 6..31 ;
PC ← UscitaALU
Quando l’istruzione è una diramazione, bisogna calcolare l’indirizzo di
destinazione e “prepararsi” a decidere se effettuare o meno il salto.
Lo spiazzamento da sommare al contenuto del registro PC è lungo 16 bit
(formato I) nel caso delle diramazioni oppure 26 bit (formato J) nel caso dei
salti, sempre con segno: nel caso della diramazione, esso è contenuto nei bit
da 16 a 31 del registro IR e quindi, prima di sommarlo al contenuto di PC,
deve esserne fatta una estensione in segno di 16 bit; invece, nel caso dei
salti, esso è contenuto nei bit da 6 a 31 del registro IR e quindi deve esserne
fatta una estensione in segno di 6 bit.
Sempre con riferimento alle diramazioni, si nota che viene scritto un
registro denominato genericamente “cond”, che appunto servirà nella fase
successiva per decidere se l’indirizzo di destinazione deve essere caricato o
meno nel registro PC. Nel caso del DLX, le uniche due istruzioni di
diramazione confrontano il registro sorgente specificato con 0, per cui
l’operatore di confronto op (specificato dal codice operativo) può essere solo
di uguaglianza (se l’istruzione è BEQZ) oppure di disuguaglianza se
l’istruzione è BNEZ). Notiamo inoltre che anche l’istruzione di scrittura del
registro “cond” richiede l’uso dell’ALU, che quindi viene impegnata due volte.
Se l’istruzione è un salto (quindi non sottoposto ad alcuna condizione),
successivamente al calcolo dell’indirizzo di destinazione viene direttamente
aggiornato il registro PC (il che invece non avviene, per le diramazioni, in
questa fase) e non ci sono altre operazioni da compiere, per cui le successive
due fasi saranno “inattive”.
E’ importante sottolineare che, sia nel caso delle diramazioni sia in quello
dei salti, lo spiazzamento che viene sommato al registro PC tiene conto del
fatto che tale registro è stato già incrementato di 4 byte nella fase
precedente,
per
cui
l’indirizzo
di
destinazione
risulta
essere
PC+4+spiazzamento.
Infine, segnaliamo che l’architettura load/store del DLX permette che la fase
di calcolo dell’indirizzo effettivo e quella di esecuzione siano riunite in una
fase unica, dato che nessuna istruzione si trova a dover calcolare l’indirizzo
e contemporaneamente eseguire una operazione su dei dati.
4. fase di accesso alla memoria o completamento della diramazione (fase
MEM)
E’ subito importare precisare che non tutte le istruzioni risultano attive in
questa fase: lo sono solo le istruzioni che fanno riferimento alla memoria. In
particolare, esistono due distinte possibilità, a seconda che l’istruzione in
corso di esecuzione preveda un caricamento/memorizzazione oppure una
diramazione. Esaminiamo ciascuna di tali possibilità:
aggiornamento: 10 luglio 2001
9
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Riferimento alla memoria (load/store):
MDR ← M[MAR] oppure M[MAR] ← MDR
In questo caso, l’istruzione prevede un trasferimento di dati tra memoria e
registri: se si tratta di un caricamento (load), i dati vengono prelevati dalla
memoria e trasferiti in MDR, mentre invece, in caso contrario
(memorizzazione, store), i dati vengono posti in memoria prelevandoli
dall’MDR (in cui sono stati posti preventivamente nella fase precedente). In
ogni caso, l’indirizzo di memoria usato è quello calcolato nella fase
precedente e conservato nel MAR. In ogni caso, il processore si preoccupa di
inviare, tramite il bus di sistema, un segnale alla memoria per indicare se
vuole leggere o scrivere un dato.
Diramazione:
if (cond) PC ← UscitaALU
Se l’istruzione corrisponde ad una diramazione e la condizione di
diramazione risulta verificata (il che equivale a richiedere che il registro di
condizione “cond”, inizializzato nella fase precedente, assuma il valore true),
l’attuale contenuto del registro PC (che in questo momento corrisponde
all’indirizzo dell’istruzione in corso sommato a 4) viene sostituito
dall’indirizzo di destinazione della diramazione, calcolato nella fase
precedente e quindi prelevato dalla uscita della ALU.
5. fase di scrittura del risultato (fase WB)
Rd ← UscitaALU oppure MDR
L’ultimo passo dell’esecuzione corrisponde nel porre il “risultato finale” nel
registro di destinazione Rd; tale risultato finale può provenire direttamente
dall’uscita della ALU (per le istruzioni di tipo aritmetico/logico) oppure dal
registro MDR (per le istruzioni di caricamento dalla memoria).
Anche per questa fase, è opportuno precisare che non tutte le istruzioni vi
risultano attive: ad esempio, non lo sono le istruzioni di memorizzazione
(store) e quelle di salto.
Prima di andare oltre, riteniamo opportuno osservare una cosa: consideriamo
l’istruzione riportata poco fa nell’ultima fase dell’esecuzione, ossia
Rd ← MDR
(si suppone evidentemente che il dato da portare nel registro Rd si trovi in MDR,
ossia sia stato prelevato dalla memoria). Questa scrittura (al pari di tutte le altre
precedentemente riportate) in realtà sottintende una serie di operazioni più semplici,
che sono le seguenti:
MDR → S1
S1 → ALU → Dest
Dest → C
C → Rd
Autore: Sandro Petrizzelli
10
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Il dato contenuto in MDR, per arrivare al registro di destinazione Rd (nel banco dei
registri), deve fare un lungo “giro” nell’unità di calcolo, passando dal bus S1 al bus
Dest (tramite la ALU) e poi da questo al latch C, il quale ci occuperà di caricare Rd.
Non esiste infatti nessun altro “percorso diretto” che colleghi MDR ed Rd.
Un altro esempio semplice è quello che corrisponde all’istruzione simbolica
MAR ← A + (IR 16 ) 16 ##IR 16..31 ;
esaminata anch’essa relativamente alla terza fase dell’esecuzione ed equivalente alla
successione delle seguenti operazioni elementari:
A → S1
(IR 16 ) 16 ##IR 16..31 → S2
S1 + S2 → Dest
Dest → MAR
Da notare che l’estensione in segno del valore immediato prelevato dal registro IR
avviene direttamente sul bus, usando dei latch appositi.
Vediamo invece adesso un esempio più completo, corrispondente all’esecuzione
della seguente istruzione:
ADD R1,R2,R3
La sequenza di operazioni elementari è la seguente:
− − − − − − − − − − − − − −1° ciclo di clock
R2 
→ A 
→ S1
R3 
→ B 
→ S2
PC = PC + 4
− − − − − − − − − − − − − −2° ciclo di clock
ALU
S1 + S2 
→ Dest
Dest 
→ C
− − − − − − − − − − − − − −3° ciclo di clock
C
→ R1
Il primo ciclo di clock (si trascura la fase precedente di prelevamento
dell’istruzione, che richiede un ulteriore ciclo di clock) corrisponde dunque alla
decodifica dell’istruzione (fase ID); il ciclo di clock successivo corrisponde
all’esecuzione vera e propria (fase EX); il terzo ciclo di clock corrisponde alla
scrittura del risultato a destinazione (fase WB), che in questo caso è il registro R1.
Evidentemente, questa sequenza vale per qualunque operazione aritmetica o logica
e non certo solo per l’istruzione di somma.
Ripetiamo ora lo stesso esempio, considerando però l’uso di un operando
immediato, pari ad esempio a 5:
ADD R1,R2,#5
La sequenza di operazioni elementari è la seguente:
aggiornamento: 10 luglio 2001
11
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
− − − − − − − − − − − − − −1° ciclo di clock
R2 
→ A 
→ S1
(IR 16 )16 # # IR 16...31 
→ S2
PC = PC + 4
− − − − − − − − − − − − − −2° ciclo di clock
S1 + S2 ALU

→ Dest
Dest 
→ C
− − − − − − − − − − − − − −3° ciclo di clock
C
→ R1
La differenza con l’esempio precedente è evidente, in quanto, al posto di caricare il
bus S2 con il valore contenuto nel registro R3, in questo caso si considera il
contenuto del registro IR corrispondente ai 16 bit meno significativi, ovviamente
esteso in segno.
Adesso consideriamo una istruzione di salto, come ad esempio la seguente:
JMP #disp
Si tratta di una istruzione di salto incondizionato, che riporta uno spiazzamento
(displacement) da sommare al registro PC per ottenere l’indirizzo della prossima
istruzione da eseguire. Le sequenza delle “operazioni” compiute (trascurando sempre
la fase iniziale di prelievo dell’istruzione), è dunque la seguente:
− − − − − − − − − − − − − −1° ciclo di clock
→ S1
PC 
→ S2
(IR 6 ) 6 # # IR 6...31 
PC = PC + 4
− − − − − − − − − − − − − −2° ciclo di clock
S1 + S2 ALU

→ Dest
→ PC
Dest 
Sono dunque sufficienti due soli cicli di clock: il primo serve alla “preparazione”
degli operandi (fase ID), mentre il secondo serve per il calcolo dell’indirizzo di salto e
la sua immediata scrittura nel registro PC (fase EX). Sarebbe invece necessario un
altro ciclo di clock nel caso di diramazione (ad esempio BEQZ R1,#disp):
bisognerebbe infatti verificare la condizione del salto e, in caso affermativo,
aggiornare il valore del registro PC per completare il salto (fase MEM).
L
Laatteen
nzzaa d
deellllee iissttrru
uzziioon
nii
I discorsi fatti nell’ultimo paragrafo ci consentono di fare alcune importanti
osservazioni circa la “latenza” delle istruzioni, ossia il tempo totale necessario per la
loro esecuzione.
In linea generale, è importante comprendere che le istruzioni hanno durata
diversa a seconda delle fasi in cui sono effettivamente attive ; non solo, ma,
Autore: Sandro Petrizzelli
12
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
sempre in linea generale, le singole fasi possono a loro volta richiede uno o più cicli
di clock. Nel caso del DLX, tuttavia, abbiamo supposto in precedenza che tutte le
istruzioni, tranne quelle per la virgola mobile, prevedano stadi di esecuzione
ciascuno di un unico ciclo di clock, per cui l’aspetto su cui dobbiamo concentrarci è
proprio il numero di stadi in cui le varie istruzioni risultano attive.
Consideriamo ad esempio una istruzione di lettura da memoria (load): questa è
l’istruzione più lunga possibile, dato che richiede il compimento di operazioni in
tutte le cinque fasi (prelievo, decodifica, esecuzione, accesso alla memoria, scrittura
dei risultati), mentre invece i salti sono le istruzioni più veloci, in quanto non
richiedono né l’accesso alla memoria dati (fase MEM) né la scrittura del banco di
registri (fase WB).
Al fine di avere una idea ancora più concreta, immaginiamo che gli accessi a
memoria (fasi IF e MEM), l’uso della ALU e l’uso di addizionatori (fase EX) richiedano
ognuno 10 ns e che gli accessi al banco dei registri (fasi ID e WB) richiedano 5 ns;
sulla base di tali dati, andiamo a confrontare la durata di istruzioni delle varie
classi, considerando in particolare le seguenti:
•
load word (LW): lettura da memoria e scrittura in un registro;
•
store word (SW): lettura da registro e scrittura in memoria;
•
add e sub (ADD e SUB): addizione e sottrazione in aritmetica intera;
•
and e or (AND e OR): operazioni logiche;
•
jump (J): salto (incondizionato) ad un indirizzo di destinazione (specificato
nell’istruzione stessa);
•
branch on equal zero (BEQZ): salto ad un indirizzo di destinazione
(specificato nell’istruzione stessa) se il contenuto del registro specificato è
pari a 0.
La tabella seguente mostra la latenza per queste istruzioni:
Istruzione
LW
SW
BEQZ
ALU
J
IF
10 ns
10 ns
10 ns
10 ns
10 ns
ID
5 ns
5 ns
5 ns
5 ns
5 ns
EX
10 ns
10 ns
10 ns
10 ns
10 ns
MEM
10 ns
10 ns
10 ns
WB
5 ns
5 ns
totale
40 ns
35 ns
35 ns
30 ns
25 ns
Quando affronteremo il problema del pipelining, vedremo che una condizione
necessaria per l’esecuzione di più istruzioni in contemporanea è che per ogni
istruzione vengano comunque eseguiti tutti gli stadi del ciclo di esecuzione, anche se
in alcuni stadi non vengono effettuate operazioni di alcun tipo, e che la latenza di
tutti gli stadi sia resa uguale alla latenza dello stadio più lento. Ad esempio, se i
valori di latenza dei vari stadi fossero quelli della tabella di poco fa, dovremmo
imporre a tutti gli stadi una durata di 10 ns, nonostante lo stadio ID e lo stadio WB,
quando attivi, richiedano invece solo 5 ns.
aggiornamento: 10 luglio 2001
13
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
IIll ““cco
on
nttrro
ollllo
o””
Introduzione
Abbiamo già osservato in precedenza che l’unità di calcolo è quella parte di
processore più difficile da progettare. Il progetto di questa unità deve partire dalla
definizione di due “oggetti”: l’insieme delle istruzioni della macchina da realizzare (ad
esempio il DLX) e lo schema dell’unità di calcolo.
L’unità di calcolo è costituita da un certo numero di componenti hardware
fondamentali (registri, bus, ALU, circuiti combinatori di vario tipo), opportunamente
connessi tra di loro e ciascuno con proprie funzionalità. Vogliamo allora vedere il
modo in cui tali componenti hardware vengono “pilotati”, ossia come ne vengono
gestite le “operazioni”, quando c’è da eseguire una istruzione. Ad esempio, vogliamo
vedere come viene avviato il caricamento di un dato in un registro, la
memorizzazione di un dato da un registro su un bus, il compimento di una
operazione da parte della ALU e così via. Vogliamo cioè esaminare la questione del
cosiddetto controllo, inteso sostanzialmente come controllo dell’unità di calcolo.
Diagramma a stati finiti descrittivo del controllo
L’azione di controllo, in un processore, viene evidentemente svolta dall’unità di
controllo situata in esso: l’unità di controllo “informa” l’unità di calcolo sulle attività
da eseguire in ogni ciclo di clock durante l’esecuzione delle istruzioni.
Dal nostro punto di vista, possiamo “vedere” tali attività specificate tramite un
diagramma a stati finiti: ogni stato del diagramma corrisponde ad un ciclo di clock
e tutte le operazioni da eseguire durante tale ciclo sono specificate dallo stato.
Affinché l’esecuzione di ogni istruzione sia portata a termine, servono sempre diversi
cicli di clock ( 8).
Tanto per chiarirci le idee sul diagramma a stati finiti rappresentativo del
controllo, consideriamo come esempio le prime due fasi dell’esecuzione delle
istruzioni del DLX, che abbiamo descritto in precedenza tramite le seguenti sequenze
di operazioni elementari:
MAR←PC;
IR←M[MAR]
A←Rs1;
B←Rs2
PC←PC+4
Il corrispondente diagramma a stati finiti è il seguente:
8
Quando parleremo di pipeline, vedremo un metodo con cui sovrapporre l’esecuzione di più istruzioni, al fine di eseguire
una istruzione per ogni ciclo di clock.
Autore: Sandro Petrizzelli
14
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
MAR ç PC
Accesso alla
memoria
incompleto
IR ç M[MAR]
Accesso alla
memoria completo
PC ç PC + 4
A ç Rs1
B ç Rs2
Parte iniziale del diagramma a stati finiti per l’esecuzione di una istruzione. Sono mostrate le
prime due fasi dell’esecuzione delle istruzioni: il prelievo dell’istruzione dalla memoria e la
decodifica dell’istruzione con la lettura dei registri. Da notare che il secondo stato viene
ripetuto fin quando l’istruzione viene interamente prelevata interamente dalla memoria.
Si nota che la prima fase (prelievo in memoria della istruzione da eseguire) risulta
“distribuita” su tre stati, ossia impiega tre cicli di clock: nel primo ciclo, il MAR viene
caricato con l’indirizzo contenuto nel PC; nel secondo ciclo, l’istruzione prelevata
dalla memoria viene posta nel registro IR; infine, nel terzo ciclo il registro PC viene
aggiornato in modo da puntare all’istruzione successiva. Nello stesso ciclo, però,
viene effettuata anche la seconda fase dell’esecuzione, in quanto vengono caricati i
due operandi dai registri Rs1 ed Rs2 rispettivamente nei registri A e B, che saranno
usati nelle fasi successive.
Facciamo notare che il passaggio dal secondo al terzo stato del diagramma richiede
che l’accesso alla memoria sia completo, ossia evidentemente che l’istruzione
richiesta venga interamente caricata nel registro IR. Questo aspetto sarà
approfondito più avanti.
Una volta noto il diagramma a stati, il passo successivo consiste nel “trasformarlo”
in una struttura fisica e qui si presentano varie alternative, in base alla tecnologia di
realizzazione, che esamineremo nei prossimi paragrafi.
Un semplice modo per valutare la complessità dell’organo di controllo corrisponde
a calcolatore il seguente prodotto:
Stati × Ingressi di controllo × Uscite di controllo
In questa espressione:
•
“Stati” indica il numero di stati dell’unità di controllo
•
“Ingressi di controllo” è il numero di segnali esaminati dall’unità di
controllo;
•
“Uscite di controllo” è il numero di uscite di controllo generate dall’unità di
controllo, inclusi i bit che specificano lo stato successivo (si veda più avanti).
aggiornamento: 10 luglio 2001
15
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Controllo dedicato (nel DLX)
Nella prossima figura viene schematizzata l’organizzazione del controllo per il DLX
nel caso si decida di usare una logica dedicata, il che significa implementare le
funzioni di controllo direttamente in hardware:
Unità di calcolo
Unità di controllo
Logica dedicata
40 bit
2(12+3+6)=221 elementi
12 bit
6 bit
Uscite di
controllo
(40 bit)
3 bit
Stato
Registro Istruzioni
Controllo dedicato di un insieme semplice di istruzioni, specificato tramite una tabella
Vediamo di capire il perché di una struttura di questo tipo. Si può verificare che il
diagramma completo degli stati del DLX contiene circa 50 stati diversi :
•
per codificare in binario ciascuno di tali stati , sono necessari 6 bit, dato
che 2 6 =64>52 (e 2 5 =32<50); l’organo di controllo riceve quindi in ingresso 6
linee di controllo, che descrivono lo stato attuale ( 9);
•
a queste linee di controllo se ne aggiungono necessariamente delle altre:
alcune linee provengono dall’unità di calcolo (ad esempio le linee di
condizione in uscita dalla ALU, per segnalare un overflow oppure un
risultato negativo o nullo o altro) e dall’unità di interfacciamento della
memoria (ne sono state considerate 3 in tutto nella figura), mentre altre
linee devono provenire dal registro istruzioni (IR), per indicare il tipo di
istruzione da eseguire di volta in volta: in particolare, si tratta di 12 linee, di
cui 6 corrispondono al codice operativo primario e altre 6 a quello esteso ( 10);
•
l’unità di controllo ha poi delle linee di uscita, dirette appunto all’unità di
calcolo, che esercitano materialmente l’attività di controllo: l’unità di calcolo
implementa (in hardware) una specie di tabella di grandi dimensioni; gli
ingressi di controllo, nel loro complesso, individuano univocamente una riga
di tale tabella, la quale riga contiene a sua volta i valori delle linee di uscita
9
La logica combinatoria dell’unità di controllo, insieme allo stato che la retroaziona, costituisce per definizione un
circuito sequenziale.
10
Gli indicatori dei registri e gli operandi immediati situati nel registro IR non hanno invece motivo di passare
per l’unità di calcolo e quindi vengono inviati direttamente all’unità di calcolo.
Autore: Sandro Petrizzelli
16
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
che pilotano, nell’unità di calcolo, le operazioni richieste dallo stato e
forniscono il numero dello stato successivo. Nella figura sono state indicate
40 uscite di controllo, ritenute sufficienti.
Tanto per fare un esempio semplice della funzione svolta dalle uscite di controllo,
soffermiamoci per un attimo sul registro PC dell’unità di calcolo. Ci si può rendere
conto facilmente che, relativamente all’architettura del DLX, tale registro deve essere
“controllato” da tre linee: una linea deve stabilire se, in un dato istante, il registro
deve acquisire il dato attualmente contenuto nel bus Dest; le altre due linee, invece,
devono specificare se il dato contenuto nel registro deve essere trasferito sul bus S1
o sul bus S2. Il discorso è ovviamente del tutto analogo per gli altri registri speciali
(MAR, MDR, IAR, Temp).
In modo del tutto analogo, il multiplexer che si trova in ingresso al registro MDR
necessita di una sola linea di controllo, che selezioni il dato da portare nel registro
MDR: può trattarsi dell’uscita della ALU (situata sul bus Dest) oppure di un dato
proveniente dalla memoria (tramite il bus dati).
Se invece consideriamo i latch A, B e C, per ciascuno di essi basterà una sola
linea: ad esempio, ogni volta che vogliamo acquisire un dato nel latch C (dal bus
Dest), dobbiamo asserire la corrispondente linea di controllo, in modo appunto da
avviare l’acquisizione.
R
Riid
du
uzziioon
nee d
deeii ccoossttii d
deellll’’h
haarrd
dw
waarree d
deell ccoon
nttrroolllloo d
deed
diiccaattoo
E’ noto che la realizzazione più semplice di una tabella si ottiene tramite una
memoria ROM: le linee di ingresso individuano una riga della memoria, la quale
produce una data configurazione per le linee di uscita. Tuttavia, il problema è che,
nell’esempio considerato nel paragrafo precedente, sarebbero necessarie 2 21 parole,
ognuna da 40 bit; si otterrebbe una memoria ROM da 10 MB, il che è decisamente
improponibile.
Fortunatamente, solo una parte limitata di questa tabella memorizza informazioni
non ridondanti, per cui è possibile ridurne le dimensioni conservando solo le righe
non ridondanti ed eliminando tutte le altre (le cosiddette condizioni don’t care della
tabella di verità della ROM), a costo naturalmente di una decodifica più complicata
degli indirizzi della tabella.
Un dispositivo hardware che assolve a questa funzione è una matrice logica
programmabile (PLA, Programmable Logic Array): tramite una PLA, si riesce a
ridurre le dimensioni dell’hardware da 2 21 parole ad appena 50, aumentando però le
dimensioni e la complessità del dispositivo per la decodifica degli indirizzi.
Nelle macchine reali, però, anche una sola PLA risulta talvolta proibitiva, in
quanto le sue dimensioni crescono in modo proporzionale al prodotto delle righe non
ridondanti con la somma degli ingressi e delle uscite. Per aggirare il problema, la
tabella complessiva viene scomposta in tabelle più piccole, ognuna implementata con
una PLA: le uscite delle varie PLA vengono poi commutate per scegliere il controllo
corretto.
P
Prreessttaazziioon
nii d
deell ccoon
nttrroolllloo d
deed
diiccaattoo
Quando si progetta in dettaglio il controllo di una macchina, gli obbiettivi che si
vogliono perseguire sono i seguenti:
•
minimizzazione del valore medio del CPI (cicli di clock per istruzione);
aggiornamento: 10 luglio 2001
17
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
•
minimizzazione della durata del periodo di clock;
•
minimizzazione del numero di dispositivi hardware richiesti dal controllo;
•
minimizzazione del tempo necessario a sviluppare un organo di controllo
che funzioni nel modo desiderato.
Il primo obbiettivo si può ad esempio ottenere riducendo il numero medio di stati
che costituiscono il percorso di esecuzione di una istruzione, dato che ciascuno stato
corrisponde ad un ciclo di clock; a sua volta, la riduzione del numero medio di stati
viene ottenuta modificando opportunamente l’unità di calcolo, al fine di riunire o
addirittura eliminare determinati stati.
Controllo microprogrammato
Nei primi calcolatori, le istruzioni per l’aritmetica, le operazioni booleane, lo
scorrimento (shifting), il confronto, i cicli, ecc. erano direttamente eseguite
dall’hardware: per ciascuna istruzione esisteva cioè uno specifico circuito hardware
addetto alla sua esecuzione. Si poteva addirittura svitare il pannello posteriore del
contenitore (il cosiddetto case) del calcolatore e individuare le componenti elettroniche
usate per ciascuna operazione.
In un calcolatore moderno a più livelli, invece, non esistono più circuiti per ogni
singola operazione: tutte le istruzioni disponibili al livello della macchina standard
(cioè appunto le istruzioni per l’aritmetica, per le istruzioni booleane, per lo shifting
e così via) sono eseguite, un passo alla volta, tramite un cosiddetto interprete, che
lavora al livello della microprogrammazione (livello L1). In generale, un interprete è
un programma (quindi si tratta di software), scritto nel linguaggio del livello a cui
lavora (ad esempio L1), che riceve in input i programmi scritti nel linguaggio di
livello superiore (L2) e li esegue, esaminando una istruzione dopo l'altra ed
eseguendo, per ognuna, la sequenza equivalente di istruzioni in L1.
Quindi, mentre nei vecchi calcolatori si potevano ad esempio individuare i
circuiti hardware dedicati appositamente alla divisione, l’equivalente nei moderni
calcolatori sarebbe quello di individuare il listato del microprogramma eseguito
dall’interprete per interpretare le istruzioni di divisione.
Anche se i programmi di un qualsiasi livello possono essere eseguiti da un
interprete e quest’ultimo può essere eseguito a sua volta da un interprete ancora di
livello inferiore, è ovvio che tale gerarchia non può procedere all’infinito (andando
verso il basso): al livello più basso deve infatti esistere una macchina fisica, con
circuiti hardware, in grado di compiere le operazioni richieste dalle istruzioni.
Dato che l’architettura del livello della microprogrammazione, chiamata
microarchitettura, è definita dall’hardware, solitamente risulta abbastanza
difficile da programmare. Il lavoro del microprogrammatore è quello di scrivere
microprogrammi, ossia programmi per controllare i registri, i bus, le ALU, le
memorie e gli altri componenti hardware della macchina. A tal fine, risulta
indispensabile la conoscenza di tutti questi dispositivi ( 11).
11
A tali dispositivi è dedicata una apposita appendice
Autore: Sandro Petrizzelli
18
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
L
Lee m
miiccrrooiissttrru
uzziioon
nii
Le microistruzioni hanno fondamentalmente due compiti:
•
specificano tutti i segnali di controllo per l’unità di calcolo;
•
decidono, in modo condizionato (ossia sulla base del verificarsi di
determinati “eventi” nonché sulla base dello “stato” della macchina), quale
istruzione debba essere eseguita in seguito.
Una volta che sono state progettate l’unità di calcolo e la memoria delle
microistruzioni (ossia il chip in cui memorizzare tutte le microistruzioni a
disposizione della macchina), il controllo diventa una attività tipicamente di
programmazione,
come
del
resto
suggerisce
lo
stesso
termine
“microprogrammazione”: questo significa, sostanzialmente, che bisogna scrivere un
interprete per l’insieme di istruzioni, ossia un programma che associ, ad ogni
istruzione, la corrispondente sequenza di microistruzioni (ossia il corrispondente
microprogramma). A sua volta, ogni microistruzione fa in modo che l’unità di calcolo
esegua un certo numero di microperazioni.
La figura seguente riporta un esempio del modo in cui può essere organizzato un
semplice controllo microprogrammato:
Unità di controllo
Memoria di
Controllo
Regist
ro
Micro
istruzi
oni
Unità di calcolo
Uscite di
controllo
(40 bit)
Registro Contatore di
Microprogramma
1
+
Circuito di selezione
dell'indirizzo
Una unità di controllo microprogrammati semplice
Quanto riportato in questa figura va ovviamente confrontato con la figura
precedente, in cui abbiamo riportato un esempio di unità di controllo dedicata. La
differenza è abbastanza evidente: l’unità di controllo è costituita da una classica
“struttura” per la decodifica e l’esecuzione di istruzioni, che in questo caso sono
ovviamente microistruzioni; tale struttura è composta da:
•
12
una memoria contenente le microistruzioni a disposizione della macchina
(memoria di controllo); ( 12)
Si tratta generalmente di una EPROM o di una EAROM, comunque di una memoria a sola lettura.
aggiornamento: 10 luglio 2001
19
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
•
un registro contenente
microistruzioni),
•
un incrementatore: si tratta di una particolare unità aritmetica e logica,
avente la particolarità di ricevere in ingresso un operando variabile ed una
costante pari ad 1 e di eseguire solo l’operazione di somma tra di essi;
•
un circuito speciale che serve per specificare la microistruzione successiva
(contatore di microprogramma e circuito di selezione dell’indirizzo).
la
microistruzione
da
eseguire
(registro
Il funzionamento del controllo microprogrammato è tale per cui, in uscita dal
registro microistruzioni, partano i segnali di controllo (40 nel nostro esempio) che
vanno a controllare e pilotare il funzionamento dell’unità di calcolo, così come
accade anche nel controllo dedicato.
F
Foon
nd
daam
meen
nttii d
deellllaa m
miiccrroop
prrooggrraam
mm
maazziioon
nee
L’uscita del registro microistruzioni è dunque costituita da un certo numero di
linee di controllo che si dirigono verso l’unità di calcolo e ne regolano e controllano
il comportamento. Ogni linea ha un compito ben preciso: ci sono ad esempio le linee
che impongono il caricamento di un registro oppure la memorizzazione del suo
contenuto in un bus, linee che vanno direttamente in ingresso alla ALU dell’unità di
calcolo e specificano l’operazione da compiere ( 13), linee che pilotano i latch in
ingresso ed in uscita alla predetta ALU e così via. Se interpretiamo ciascuna linea
come un bit (che può valere 0 oppure 1 a seconda che sia asserita o meno), l’insieme
delle linee costituisce una microistruzione.
In linea di massima, dato che ciascuna linea ha un compito ben preciso, dal punto
di vista dell’hardware risulta indifferente il modo in cui raggruppiamo le linee per
formare una microistruzione; al contrario, per rendere più “leggibile” il significato
delle microistruzioni, ci conviene sistemare sempre vicine le linee che svolgono
compiti più o meno correlati: in questo modo, possiamo individuare una struttura
ben precisa delle microistruzioni, suddividendole in campi, dove ogni campo va
visto come un insieme di linee che svolgono funzioni analoghe.
La figura seguente mostra un semplice esempio di microistruzione ad 8 campi:
Destinazione
Operazione
ALU
Sorgente 1 Sorgente 2
Costante
Misto
Condizione
Indirizzo di
salto
Esempio di microistruzione con 8 campi: il nome attribuito ai vari campi riflette evidentemente
la loro funzione.
Possiamo immaginare la microprogrammazione come un modo per associare, a
ciascun campo, una opportuna configurazione di bit, il che somiglia molto alla
programmazione di istruzioni in linguaggio macchina ( 14).
13
A tal proposito, ricordiamo una cosa: la ALU è fatta in modo che, dati gli operandi in ingresso, vengano effettuate su di
esse TUTTE le operazioni (aritmetiche e logiche) che la ALU è in grado di fare; tuttavia, dato che interessa una sola di
queste operazioni per volta, l’ “azione” delle linee di controllo è semplicemente quella di selezionare il risultato di
interesse e porlo in uscita dalla ALU.
14
Per distinguere le “microistruzioni” dalle “istruzioni in linguaggio macchina”, queste ultime vengono talvolta chiamate
macroistruzioni.
Autore: Sandro Petrizzelli
20
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Facendo ancora riferimento al precedente esempio di unità di controllo
microprogrammata, osserviamo che esistono due modi distinti per specificare la
microistruzione successiva da eseguire:
•
nell’esempio da noi raffigurato, si è usato un registro contatore di
microprogramma (talvolta indicato con µ PC), del tutto analogo al registro
PC per le istruzioni in linguaggio macchina: esso contiene l’indirizzo della
prossima microistruzione da eseguire;
•
l’alternativa sarebbe quella di riservare un campo, in ogni microistruzione,
in cui riportare l’indirizzo della microistruzione successiva. Questo
ovviamente comporta un allungamento dell’istruzione, ma talvolta è una
soluzione da preferirsi, tanto che, in alcune macchine, vengono previsti
addirittura più campi per l’istruzione successiva, in modo da poter gestire
anche le diramazioni.
La memoria di controllo, situata nell’unità di controllo microprogrammata, è la
parte fisica più appariscente nel controllo microprogrammato ed è per questo che la
maggior parte delle tecniche usate per ridurre il costo dell’hardware sono rivolte
proprio ad essa. Si possono individuare almeno tre tecniche per ridurre le
dimensioni della memoria di controllo:
•
ridurre il numero di microistruzioni;
•
ridurre la lunghezza della microistruzioni;
•
entrambe le cose.
Mentre il costo dell’unità di controllo microprogrammata viene quantificato
tramite le dimensioni della memoria di controllo, le prestazioni vengono invece
generalmente espresse tramite il CPI (che in questo caso corrisponde ai cicli di clock
per microistruzione). Ogni microprogrammatore conosce la frequenza con cui si
presentano le singole istruzioni in linguaggio macchina e quindi sa dove e come
“spendere meglio il tempo”, ottimizzando in velocità le istruzioni che consumano la
maggior parte del tempo di esecuzione ed ottimizzando in spazio le istruzioni
rimanenti.
Esecuzione di una istruzione
Abbiamo dunque detto che, in un microprocessore microprogrammato,
l’esecuzione di una istruzione letta dalla memoria principale richiede
l’esecuzione di un opportuno microprogramma residente nella memoria di
controllo :
•
ogni microprogramma che interpreta una istruzione è composto da
microistruzioni;
•
ogni microistruzione controlla
elementari (microoperazioni);
•
ogni microoperazione è espressa da un microordine rappresentato da un
opportuno bit, settato ad 1, nella microistruzione.
aggiornamento: 10 luglio 2001
l’esecuzione
21
di
una
o
più
operazioni
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
La memoria di controllo è gestita dall’unità di controllo microprogrammata, la
quale quindi presiede allo svolgimento delle seguenti operazioni:
•
in primo luogo, genera l’indirizzo della prima microistruzione del
microprogramma relativo all’istruzione appena letta (situata nel registro IR
dell’unità di calcolo). Tale indirizzo viene generato dal circuito di selezione
dell’indirizzo, sulla base del codice operativo dell’istruzione;
•
in secondo luogo, genera gli indirizzi delle successive microistruzioni
componenti il microprogramma da eseguire. Questa funzione viene
realizzata dal registro µPC (micro-program counter), che funziona in modo
del tutto analogo al registro PC per le istruzioni in linguaggio macchina.
Nel caso più generale, il registro µPC viene autoincrementato per generare
l’indirizzo della microistruzione successiva, fino a quando non viene letto il
microordine denominato End Of Operation (EOP): questo microordine
segnala il termine del microprogramma, ed è seguito dalla microoperazione
0→
→ µ PC, che serve per ripartire con la istruzione successiva; in particolare,
si suppone che l’indirizzo 0 della memoria di controllo corrisponda a
quello del microprogramma che pilota la fase di fetch dell’istruzione
successiva . Il contenuto di µPC può del resto essere alterato dal verificarsi
di particolari condizioni, segnalate dalla logica di selezione del prossimo
indirizzo tramite alcuni bit di controllo: questi bit, quando attivati, possono
alterare la condizione di autoincremento di µPC, rendendo così possibili le
diramazioni (a livello di microprogramma);
•
infine, l’unità di controllo attiva le linee di controllo indicate dalla
microistruzione in corso di esecuzione, le quali abilitano l’esecuzione dei
microordini richiesti per l’esecuzione dell’istruzione attualmente nel
registro IR.
Per semplificare la realizzazione della memoria di controllo nonché la
configurazione delle linee di controllo, i microordini sono raggruppati per categorie
e nelle microistruzioni sono organizzati in campi.
Scendendo ancora in maggiore dettaglio, l’interpretazione di una istruzione
richiede l’esecuzione dei seguenti passi:
•
l’istruzione da eseguire viene prelevata dalla memoria e portata nel registro
IR (fase di fetch);
•
la logica di selezione dell’indirizzo genera, mediante una tabella di
puntatori (detta mapping memory o anche tabella di mapping), l’indirizzo,
associato al codice operativo dell’istruzione in IR, della prima
microistruzione del microprogramma da eseguire; tale indirizzo viene posto
nel registro µPC;
Autore: Sandro Petrizzelli
22
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Registro
Istruzioni
IR
Tabella di
mapping
Contatore di
microprogramma
MAP
Memoria di
controllo
FW
uPC
Registro
Microistruzioni
MIR
Schematizzazione del processo di individuazione della prima microistruzione corrispondente
all’istruzione contenuta nel registro IR
•
la microistruzione così indirizzata viene trasferita dalla memoria di
controllo nel registro delle microistruzioni (MIR);
•
viene infine attivata la fase di esecuzione della istruzione, pilotata dalle
linee di controllo in uscita dal registro delle microistruzioni.
Fondamentalmente, le microistruzioni corrispondono a trasferimento di
dati tra registri, specifiche parole di memoria, con o senza il tramite della
ALU.
Esempio: microprogramma
istruzione
per
la
fase
di
fetch
di
una
Supponiamo che tutte le istruzioni e i dati del nostro calcolatore abbiano la stessa
lunghezza ed occupino una sola locazione di memoria principale. Sotto questa ipotesi, la
sequenza di microistruzioni (microprogramma) che realizza la fase di fetch di una
istruzione è il seguente:
PC → MAR
M[MAR] → MDR
MDR → IR
MAP(IR CO ) → µPC
La prima microistruzione fa in modo che il contenuto del registro MAR venga caricato con
l’indirizzo contenuto nel registro PC. Il contenuto del MAR viene usato dal processore per
interfacciarsi al bus degli indirizzi e quindi puntare alle locazioni di memoria principale: la
locazione di memoria puntata dal MAR viene quindi caricata, tramite il bus dei dati,
nell’MDR, che a sua volta viene “copiato” nel registro IR. Infine, l’ultima istruzione individua,
nella tabella di mapping, l’indirizzo della prossima microistruzione da eseguire: tale indirizzo
viene ricavato sulla base del codice operativo dell’istruzione appena caricata nel registro IR
(da cui la simbologia IR CO ). L’indirizzo ottenuto viene quindi caricato nel registro µPC.
Quella appena fatta è una descrizione precisa ma comunque ancora non dettagliatissima
di quello che avviene effettivamente nell’unità di calcolo. Infatti, consideriamo ad esempio la
prima microistruzione, PC→MAR: non essendoci un collegamento diretto tra il registro PC ed
il registro MAR, il passaggio dell’indirizzo avviene tramite la ALU; in particolare, si ha
quanto segue:
aggiornamento: 10 luglio 2001
23
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
PC 
→ S1
S1 ALU

→ Dest
Dest 
→ MAR
Tutte queste microoperazioni sono pilotate sempre dalla microistruzione: come si è già
detto, ad ogni microoperazione sono assegnate un certo numero di linee di controllo, le
quali, tramite anche apposita circuiteria, non solo pilotano ad esempio il caricamento di un
registro su un bus o viceversa, ma anche la temporizzazione.
R
Riid
du
uzziioon
nee d
deeii ccoossttii d
deellll’’h
haarrd
dw
waarree
Ricordiamo che le tecniche di controllo, sia dedicate sia microprogrammate, sono
valutate in base al loro effetto sui seguenti fattori:
•
costo dell’hardware;
•
durata del ciclo di clock;
•
CPI;
•
tempo di progetto.
Per quanto riguarda i costi, abbiamo già osservato che sono determinati in
massima parte dalla dimensione della memoria di controllo ed è quindi tale
dimensione che deve essere ottimizzata. A tal proposito, si possono sia ridurre il
numero di microistruzioni sia ridurre la lunghezza delle microistruzioni sia fare
entrambe le cose. Nei prossimi paragrafi vedremo come ottenere questi obbiettivi,
mentre nei paragrafi ancora successivi vedremo come ridurre il CPI.
Codifica delle linee di controllo
L’approccio migliore per ridurre la dimensione della memoria di controllo
consiste nei seguenti passaggi:
•
per prima cosa, bisogna scrivere il microprogramma completo, in notazione
simbolica;
•
successivamente, per ogni istruzione del microprogramma, bisogna
esaminare la configurazione dei bit (corrispondente a quella delle linee di
controllo);
•
tramite questo esame e le relative misure, si possono identificare gruppi di
bit di controllo che possono essere codificati tramite campi di dimensione
inferiore.
Ad esempio, abbiamo visto in precedenza che il bus S1 dell’unità di calcolo del
DLX può essere alimentato da 6 diversi dispositivi: il latch A e i 5 registri speciali
(Temp, PC, IAR, MAR e MDR); in linea di massima, ci servono allora 6 linee di
controllo per controllare il caricamento di tale bus; possiamo ridurre tale numero a
3, in quanto, con 3 bit possiamo codificare 2 3 =8 possibilità, ossia due in più
rispetto a quelle necessarie. Naturalmente, questa modifica, pur facendo
risparmiare 3 bit per istruzione e pur non influendo negativamente sul CPI,
Autore: Sandro Petrizzelli
24
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
richiede comunque un costo aggiuntivo dell’hardware, dato che dobbiamo prevedere
un circuito decodificatore con 3 ingressi ed 8 uscite (di cui due inutili):
x=1
Unità di
Controllo
y=0
3*23
z=0
0
1
2
3
4
5
6
7
0
0
0
0
1
0
0
0
Unità di
Calcolo
Decoder 3*8
Uso di un circuito decodificatore 3*23 : la configurazione binaria delle linee in ingresso
determina l’unica linea in uscita che deve essere asserita. L’esempio riportato nella figura
prevede in ingresso la configurazione 100, per cui attiva la linea di uscita numero 4
Con un simile circuito, la configurazione delle 3 linee di controllo in ingresso
determina quale, delle 8 linee di controllo in uscita, debba essere attivata: si tratterà
evidentemente di quella che individua il registro con cui caricare il bus S1.
Questo incremento di costo per il decodificatore è ampiamente compensato dal
risparmio di 3 bit sull’ampiezza della parola di memoria di controllo.
La tecnica appena descritta per la riduzione della dimensione dei campi delle
microistruzioni prende il nome di codifica delle linee di controllo.
Un ulteriore miglioramento si può ottenere individuando eventuali linee che
vengono asserite soltanto di rado nella medesima microistruzione: se ci sono, le si
può codificare tutte insieme (tramite cioè una sola linea), pagando ovviamente il
prezzo che, nei cari rari in cui vengono asserite, servono due microistruzioni anziché
una sola. In questo modo, la lunghezza delle microistruzioni viene ridotta, ma ne
viene aumentato il numero: se si ottiene, complessivamente, una riduzione della
dimensione della memoria di controllo, allora si tratta di una strada sicuramente
perseguibile.
Le tecniche di codifica delle linee di controllo hanno del resto anche alcuni
svantaggi. Ad esempio, se una linea di controllo codificata fa parte di un percorso
con ritardo critico oppure se i dispositivi fisici da essa controllati fanno parte di un
percorso con ritardo critico, il tempo del ciclo di clock sicuramente ne soffrirà.
Un altro svantaggio, meno immediato del precedente, è quello che invece potrebbe
verificarsi a seguito dell’introduzione di una nuova versione del microcodice: ad
esempio, se tale nuova versione può determinare con più frequenza, rispetto alla
versione precedente, situazioni in cui talune linee di controllo codificate vengono
attivate nella stessa microistruzione, si rischierebbe di abbassare le prestazioni e/o
sarebbero richieste modifiche della struttura hardware, il che potrebbe aumentare la
durata del ciclo di sviluppo.
Formati multipli di microistruzioni
Nei due precedenti paragrafi abbiamo dato per scontato che le microistruzioni
avessero un unico formato, per cui non era necessario prevedere un codice operativo
che ne identificasse le caratteristiche. Al contrario, un modo per diminuire
ulteriormente la dimensione delle microistruzioni è proprio quello di prevedere
formati differenti, nel qual caso risulta necessario l’uso di un codice operativo che
aggiornamento: 10 luglio 2001
25
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
distingua un formato dall’altro. Per evitare confusione nella terminologia, questo
nuovo campo prende il nome di campo di formato.
Lo scopo del campo di formato è semplice: dato che ciascun formato potrà usare
tutte o parte delle linee di controllo, assegnando valori opportuni alle linee utilizzate,
il campo di formato assegna, a tutte le linee non specificate, il loro valore di default,
in modo da non dover modificare nient’altro della macchina.
Se l’uso del campo di formato consente di ridurre le dimensioni della memoria di
controllo, esso richiede altresì un proprio costo in termini di prestazioni, perché
vengono eseguite più microistruzioni: infatti, se un microprogramma usa un unico
formato di microistruzioni, tale formato può specificare qualsiasi combinazione di
operazioni dell’unità di controllo e richiede meno cicli di clock rispetto ad un
microprogramma con microistruzioni a formato variabile, che per loro natura
possono identificare ciascuna un numero ristretto di operazioni.
Del resto, le macchine con parole di memoria di piccole dimensioni risultano
sempre più economiche in quanto i chip, pur avendo un numero maggiore di parole,
hanno “larghezza” (intesa appunto come dimensione delle parole) minore: ad
esempio, servono molti meno chip per una memoria da 16384 parole (16K) da 24 bit
che non per una di 4096 prole (4 K) da 96 bit.
L’approccio con molte parole di memoria di piccola dimensione viene spesso detto
microcodice verticale, mentre invece quello con poche parole ma di grandi dimensioni
viene detto microcodice orizzontale. Anche se questa terminologia si può spiegare
facilmente, per evitare confusione noi parleremo invece, rispettivamente, di codifica
massima e codifica minima.
Ovviamente, l’uso di parole piccole di memoria presuppone che i campi delle
microistruzioni siano codificati secondo i criteri del paragrafo precedente e che
quindi le linee di controllo in uscita dall’unità di controllo vadano a pilotare dei
decoder che poi a loro volta pilotano effettivamente l’unità di calcolo. Viceversa, l’uso
di parole grandi di memoria equivale all’uso di microistruzioni molto lunghe (molte
linee) e quindi non sono necessari i decoder.
A volte capita che i progettisti di una macchina a codifica minima non riescano a
disporre di memorie RAM con numero piccolo di parole, nel qual caso essi finiscono
col progettare microistruzioni di grandi dimensioni che però occupano un gran
numero di parole nella memoria di controllo. In questi casi, dato che il costo
dell’hardware dipende dal numero e dimensione delle parole e non certo da quante di
esse risultano effettivamente occupate, alla fine si rischia di arrivare ad avere
memorie di controllo molto più grandi di quanto ci si aspettasse o comunque rispetto
ad altri esempi di macchine dello stesso tipo.
Aggiunta di
microcodice
controllo
dedicato
per
la
condivisione
di
Abbiamo già osservato che un altro possibile approccio per la riduzione della
memoria di controllo consiste nel diminuire non tanto la dimensione delle
microistruzioni, quanto il loro numero. A tal fine, si potrebbe ad esempio ricorrere
ai cosiddetti microsottoprogrammi: si tratta di porzioni di microcodice a se stanti,
che possono essere richiamate in qualunque occasione e quindi vanno scritte una
sola volta.
Un miglioramento particolarmente decisivo si può però ottenere tramite l’uso di
un controllo dedicato da affiancare a quello microprogrammato:
•
ad esempio, molte microarchitetture prevedono che alcuni bit del registro
delle microistruzioni possano specificare il registro corretto;
Autore: Sandro Petrizzelli
26
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
•
analogamente, altri bit del registro delle microistruzioni possono indicare
all’ALU l’operazione da compiere.
Chiaramente, queste e altre facilitazioni analoghe sono comunque sottoposte al
controllo microprogrammato e possono essere attivate tramite un valore speciale in
un campo apposito delle microistruzioni.
C’è del resto uno svantaggio anche nell’uso del controllo dedicato: esso potrebbe
aumentare la durata del ciclo di sviluppo della macchina, dato che, pur non
richiedendo una programmazione particolarmente complessa, rende comunque
necessario lo sviluppo di circuiti integrati e/o di schede per la messa a punto.
R
Riid
du
uzziioon
nee d
deell C
CP
PII
Fino ad ora abbiamo dunque trattato alcune tecniche per ridurre i costi di una
unità di controllo microprogrammato, aventi tutte lo scopo di ridurre la dimensione
della memoria di controllo. Vogliamo invece adesso occuparci di tre tecniche per
migliorare le prestazioni e, in particolare, per ridurre il CPI.
Uso di microcodice specializzato
I microprogrammatori validi sanno sempre quando è opportuno scrivere
microistruzioni corte e quando invece scrivere microistruzioni più lunghe (che
quindi includano più informazioni). Un tipico esempio consiste nell’usare
microistruzioni aggiuntive e specializzate per le istruzioni che si presentano con
maggior frequenza, in modo da ridurre il CPI.
Per individuare le istruzioni candidate ad essere eseguite tramite microcodice
specializzato, basta studiare le stime delle distribuzioni delle istruzioni (come
quelle esaminate nei capitoli precedenti) ed individuare le istruzioni con frequenza
più elevata.
Aggiunta di controllo dedicato
L’aggiunta di un controllo dedicato, affiancato a quello microprogrammato, può sia
ridurre i costi sia migliorare le operazioni. Ci sono vari esempi in proposito:
•
alcune macchine recenti riducono il CPI usando un codice specializzato per i
trasferimenti di dati tra processore e memoria e per l’effettuazione di somme
di tipo registro-registro o registro-memoria;
•
altre macchine tendono invece ad ottimizzare l’interfaccia del processore con
la memoria, in base alle seguenti considerazioni: in molte macchine, si fa in
modo che il microcodice esegua in continuazione test e salti fin quando la
memoria non risulta pronta; tuttavia, a causa dell’inevitabile ritardo tra
l’istante in cui una condizione diviene vera e l’istante in cui la
microistruzione successiva viene letta, questa tecnica può comportare
l’aggiunta di un ciclo di clock per ogni accesso alla memoria; molte
macchine aggirano allora il problema facendo andare in stallo la
microistruzione che tenta di accedere al registro MBR prima che l’operazione
sia stata completata ( 15); nell’istante in cui il riferimento a memoria è stato
15
In generale, si verifica uno stallo quando una istruzione deve arrestarsi per uno o più cicli di clock in attesa
della disponibilità di una qualche risorsa.
aggiornamento: 10 luglio 2001
27
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
completato, la microistruzione che richiede il dato può essere portata a
termine, evitando il ritardo di clock aggiuntivo per accedere alla memoria di
controllo.
Uso del “parallelismo”
In alcuni casi, si può ridurre il CPI eseguendo più operazioni contemporanee
tramite una sola microistruzione, nel qual caso si parla appunto di parallelismo.
Questa tecnica solitamente richiede microistruzioni più lunghe e può portare dei
benefici.
Nonostante questo, non sempre tali benefici si verificano: un noto esempio di
guadagno potenziale non realizzato fu un processore molto simile all’ Intel 8086,
caratterizzato però dall’avere un bus supplementare all’interno dell’unità di controllo;
la presenza di tale bus richiedeva 6 bit in più nelle microistruzioni, ma rendeva
possibile ridurre la fase di esecuzione, da 3 a 2 cicli di clock, in molte
macroistruzioni comuni del processore 8086; tuttavia, le macroistruzioni più comuni
erano raggruppate in macroistruzioni che invece non potevano trarre alcun vantaggio
da questa ottimizzazione, per cui venivano comunque eseguite tutte a velocità più
bassa.
Questione degli accessi incompleti alla memoria
Abbiamo accennato, nei precedenti paragrafi, alla questione degli accessi
incompleti alla memoria: quando la CPU richiede il prelievo di un dato o di una
istruzione dalla memoria, l’accesso alla memoria si dirà incompleto fino a quando
tale dato o tale istruzione non sarà stata interamente trasferita all’interno della CPU,
dopodiché esso sarà completo. Analogo discorso, ovviamente, quando c’è da scrivere
un dato in memoria (mentre invece le istruzioni possono solo essere prelevate).
Si usa spesso la seguente terminologia:
•
tempo di accesso a memoria è il tempo che intercorre dal momento in cui
il processore, tramite il MAR, invia l’indirizzo sul bus degli indirizzi fino al
momento in cui si ottiene il dato richiesto, in forma stabile, nell’MDR (per i
dati) o nell’ IR (per le istruzioni);
•
si definisce invece ciclo di memoria il tempo necessario per poter effettuare
due operazioni consecutive di accesso alla memoria. In pratica, quindi, una
volta completato un accesso a memoria, deve passare un certo tempo prima
di poter attivare un eventuale successivo accesso alla memoria: tale tempo è
la differenza tra il ciclo di memoria ed il tempo di accesso alla memoria.
Tempo di accesso
alla memoria
Ciclo di memoria
istante di
accesso alla
memoria
Autore: Sandro Petrizzelli
istante di
accesso alla
memoria
28
tempo
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Osserviamo inoltre che, fissato l’istante in cui la CPU pone sul bus l’indirizzo del
dato richiesto, è necessario comunque attendere leggermente prima di attivare la
lettura o la scrittura della memoria. Spesso, vengono usate coppie di stadi invertenti
in cascata al fine di ritardare l’esecuzione di talune operazioni, per garantire che
esse vengano eseguite solo dopo altre operazioni ad esse “propedeutiche”.
A
Arrcch
hiitteettttu
urraa aad
d aaccccu
um
mu
ullaatto
orree
Descrizione dell’architettura
Mentre il DLX precedentemente introdotto è una architettura di tipo load/store,
vogliamo ora parlare brevemente di una architettura ad accumulatore. Si tratta di
un qualcosa che tutto sommato “somiglia” al DLX, con la differenza, ovviamente, che
manca del tutto il banco di registri ed è sostituito dall’unico registro accumulatore.
L’architettura è la seguente:
Data Bus
Acc
MDR
fetch
IR
MAP
1
ROM di
microprogramma
Tabella di
mapping
uPC
FW
PC
Incrementatore
del registro PC
+
MAR
Address Bus
Semplice architettura ad accumulatore. Da notare che, per semplicità, non sono stati riportati i
latch in ingresso all’ALU. Non è stato nemmeno indicato un registro denominato X, che
introdurremo nel prossimo paragrafo e che può essere considerato come una “replica”
dell’accumulatore, ossia un registro con le stesse connessioni dell’accumulatore ma con
funzioni ben diverse
La descrizione di questa architettura appare abbastanza superflua, proprio perché
rispecchia abbastanza quanto già detto nei precedenti paragrafi per il DLX, con le
dovute differenze. Ad ogni modo, la giustificazione di alcune tra le connessioni qui
riportate sarà fornita nel paragrafo seguente, in cui si discuterà l’implementazione
delle varie modalità di indirizzamento.
aggiornamento: 10 luglio 2001
29
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Modalità di indirizzamento e loro implementazione
Nel capitolo 3 abbiamo già avuto modo di illustrare le principali modalità di
indirizzamento usati dalle istruzioni in linguaggio macchina. Tuttavia, l’analisi fatta
è stata del tutto generale, in quanto non ha tenuto conto affatto della particolare
struttura hardware del calcolatore su cui realizzare l’indirizzamento. Vogliamo allora
approfondire questo aspetto, basandoci su una semplice architettura ad
accumulatore del tipo presentato nel paragrafo precedente.
Per i nostri esempi, consideriamo una istruzione di caricamento (load) di un
dato dalla memoria. Supponiamo, ad esempio, di voler specificare l’operando tramite
l’indirizzamento diretto (o assoluto): l’istruzione simbolica è allora del tipo
LDA A
Per descrivere dettagliatamente quello che avviene a livello dell’hardware,
possiamo usare il “solito” linguaggio di descrizione dell’hardware: allora, l’istruzione
LDA A equivarrebbe banalmente a
M[A] → Acc
Il contenuto della locazione di memoria il cui indirizzo è A viene collocato
nell’accumulatore. In realtà, l’architettura che stiamo considerando è più complessa,
per cui anche il numero di operazioni elementari compiute (il cosiddetto
microprogramma) è maggiore. Si ha quanto segue:
PC+L (IR) → PC
IR OP → MAR
M[MAR] → MDR
MDR → ACC
0 → µPC
Prima di tutto, viene incrementato il registro PC, in modo da puntare all’istruzione
successiva (supponendo che le istruzioni siano memorizzate sequenzialmente nella
memoria). Successivamente, l’operando dell’istruzione attuale, contenuto nel registro
IR, viene caricato nel registro MAR, il quale viene usato per indirizzare la memoria.
La locazione di memoria indirizzata dal MAR viene dunque caricata nell’MDR, che a
sua volta viene “copiato” nell’accumulatore. L’ultima istruzione presuppone che
l’indirizzo corrispondente alla microistruzione che comanda il fetch, nella ROM di
microprogramma, sia l’indirizzo 0 e quindi provvede a caricare tale indirizzo nel
registro µPC.
A quest’ultimo proposito, osserviamo che la fase di fetch di una istruzione (che è
uguale per tutte le istruzioni) si ottiene nel modo seguente (microprogramma di fetch):
PC → MAR
M[MAR] → MDR
MDR → IR
MAP(IR CO ) → µPC
Il registro MAR viene caricato con l’indirizzo contenuto nel registro PC e viene
usato per indirizzare la memoria; la locazione di memoria indirizzata dal MAR viene
dunque caricata nell’MDR, che a sua volta viene “copiato” nel registro IR; l’ultima
istruzione individua, nella tabella di mapping, l’indirizzo corrispondente al codice
Autore: Sandro Petrizzelli
30
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
operativo dell’istruzione appena caricata nel registro IR e provvede a caricare tale
indirizzo nel registro µPC.
Adesso consideriamo una istruzione di caricamento di un operando immediato:
LDA #A
Questa istruzione ha l’effetto di portare il valore A nell’accumulatore (A→Acc),
tramite il seguente microprogramma:
PC+L (IR) → PC
IR OP → ACC
0 → µPC
Adesso consideriamo il caso di indirizzamento indiretto:
LDA (A)
Il senso di tale istruzione è M[M[A]]→Acc. Il corrispondente microprogramma è
allora il seguente:
PC+L (IR) → PC
IR OP → MAR
M[MAR] → MDR
MDR → MAR
M[MAR] → MDR
MDR → ACC
0 → µPC
Questa sequenza di istruzioni spiega il motivo per cui, nell’architettura di esempio
che stiamo considerando, esiste un collegamento diretto tra MAR e MDR: senza di
esso, il programmatore non potrebbe utilizzare la modalità di indirizzamento
indiretto. Questo collegamento sussiste anche nel DLX, dove però viene effettuato
passando tramite la ALU: a livello concreto, il “passaggio” nel MAR del valore
contenuto in MDR avviene tramite una operazione fittizia della ALU, realizzata ad
esempio tramite due invertitori posti in cascata ( 16).
Adesso consideriamo il cosiddetto indirizzamento indicizzato:
LDA A,X
Il senso di tale istruzione è M[X+A]→Acc, dove X è un registro “parallelo”
all’accumulatore e che si suppone abbia le stesse sue connessioni. Il corrispondente
microprogramma è il seguente:
PC + L (IR) → PC
X + IR OP → MAR
M[MAR] → MDR
MDR → ACC
0 → µPC
16
E’ ovvio che due stadi invertitori posti in cascata costituiscono un unico stadio detto buffer, che cioè riceve un ingresso
e lo presenta invariato in uscita dopo un certo tempo (quello necessario affinché i segnali si propaghino attraverso i due
stadi).
aggiornamento: 10 luglio 2001
31
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Questa sequenza tiene ovviamente conto del fatto che A è un indirizzo di memoria
situato nel campo operandi dell’istruzione (significa che A=IR OP nella notazione
utilizzata). Notiamo inoltre che il risultato prodotto dalla ALU con la seconda
operazione elementare (X + IR OP → MAR) viene portato direttamente nel registro
MAR. Questo fatto serve sia a giustificare l’esistenza di un collegamento diretto tra
uscita dell’ALU e registro MAR sia anche ad evidenziare quanto sia importante, in
qualsiasi architettura, l’instradamento dei dati: generalmente, l’implementazione
dell’instradamento risulta più costosa dei singoli componenti.
Una variante del caso precedente è il cosiddetto indirizzamento indicizzato
indiretto:
LDA (A,X)
Il senso di tale istruzione
microprogramma è il seguente:
è
M[M[X+A]]→Acc,
per
cui
il
corrispondente
PC + L (IR) → PC
X + IR OP → MAR
M[MAR] → MDR
MDR → MAR
M[MAR] → MDR
MDR → ACC
0 → µPC
Una ulteriore variante è il cosiddetto indirizzamento indiretto indicizzato:
LDA (A),X
Il senso di tale istruzione
microprogramma è il seguente:
è
M[M[A]+X]→Acc,
per
cui
il
corrispondente
PC + L (IR) → PC
IR OP → MAR
M[MAR] → MDR
X+MDR → MAR
M[MAR] → MDR
MDR → ACC
0 → µPC
Questa nuova sequenza serve a giustificare il perché anche il contenuto del
registro MDR possa essere portato direttamente ad uno degli ingressi della ALU.
Consideriamo ora l’indirizzamento indicizzato con post-incremento:
LDA A,X+
Il senso di tale istruzione corrisponde a due operazioni successive: prima si
effettua M[A+X]→Acc come nell’indirizzamento indicizzato, dopodiché si effettua
l’incremento del registro X di una quantità d prefissata (implicita nel codice
operativo e posseduta, come costante, dai circuiti costituenti la ALU). Il
corrispondente microprogramma è il seguente:
PC + L (IR) → PC
Autore: Sandro Petrizzelli
32
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
X + IR OP → MAR
M[MAR] → MDR
MDR → ACC
X + d → X
0 → µPC
La situazione duale della precedenza è l’indirizzamento indicizzato con predecremento:
LDA A,-X
Il senso di tale istruzione corrisponde ancora a due operazioni successive: prima si
effettua X-d→X e poi M[A+X]→Acc. Il corrispondente microprogramma è il seguente:
PC + L (IR) → PC
X – d → X
X + IR OP → MAR
M[MAR] → MDR
MDR → ACC
0 → µPC
Infine, esaminiamo una istruzione di memorizzazione di un dato in memoria,
usando ad esempio l’indirizzamento diretto:
STA A
Il senso di questa istruzione è ovviamente ACC→M[A], per cui il corrispondente
microprogramma è fatto nel modo seguente:
PC+L (IR) → PC
IR OP → MAR
ACC → MDR
MDR → M[MAR]
0 → µPC
Istruzioni di salto
Proseguendo sulla falsa riga del paragrafo precedente, vogliamo ora esaminare i
microprogrammi relativi alle istruzioni di salto, sempre con riferimento
all’architettura ad accumulatore (che, per la verità, nelle istruzioni di salto non
influisce minimamente, dato che l’accumulatore non viene coinvolto).
Come prima istruzione interessante da esaminare, consideriamo quella di salto
incondizionato (jump), che forza il contenuto del registro PC ad un valore stabilito:
ad esempio, l’istruzione
JMP α
equivale semplicemente ad α→PC. Il corrispondente microprogramma è allora fatto
nel modo seguente:
aggiornamento: 10 luglio 2001
33
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
PC+L (IR) → PC
IR OP → PC
0 → µPC
Questo microprogramma giustifica la linea di collegamento tra il registro IR ed il
registro PC: serve a trasferire direttamente in quest’ultimo l’indirizzo di salto. Tra
l’altro, si noti che la prima microistruzione risulta inutile, in quanto il PC verrà
aggiornato con l’indirizzo di salto nella microistruzione successiva.
Notiamo inoltre che il salto appena considerato non solo è incondizionato, ma
anche senza rientro. Al contrario, quando il salto viene fatto perché corrisponde ad
una chiamata di procedura (istruzione JSR, Jump SubRoutine), il rientro sarà
necessario (al termine della procedura stessa). Per realizzare un meccanismo di
questo tipo, bastano due accorgimenti:
•
in primo luogo, prima ancora di effettuare il salto, bisogna salvare
l’indirizzo di rientro del salto, che sarà l’indirizzo dell’istruzione successiva
a quella di salto. Tale indirizzo andrà posto in uno stack;
•
in secondo luogo, tutte le procedure devono avere, come istruzione finale,
una istruzione di tipo RFS (Return From Subroutine), che ha appunto lo
scopo di forzare il contenuto del registro PC al valore di rientro contenuto
nello stack. In pratica, l’istruzione RFS è un salto incondizionato del tipo
visto prima, con la differenza che l’indirizzo di salto deve essere preso dallo
stack e non è quindi contenuto nel “corpo” dell’istruzione.
Viene ora spontanea una domanda: in una architettura ad accumulatore, dove si
trova lo stack? L’unica possibilità è quella di averlo in memoria: bisogna cioè gestire
un’area di memoria come se fosse uno stack, usando due puntatori (uno per il fondo
dello stack e l’altro per la cima, quest’ultimo chiamato stack pointer) e le operazioni
di push (inserimento di un elemento nello stack) e di pop (estrazione di un elemento
dallo stack).
Sulla base di queste considerazioni, il microprogramma equivalente ad una
istruzione del tipo JSR α è fatto nel modo seguente:
PC+L (IR) → PC
PC → M[SP]
SP + L PC → SP
α → PC
0 → µPC
La prima istruzione serve a calcolare l’indirizzo della istruzione sequenzialmente
successiva a quella di salto, ossia sostanzialmente l’indirizzo di rientro dalla
subroutine; tale indirizzo viene inserito (push) nello stack e precisamente nella
locazione di memoria puntata da un apposito registro SP (stack pointer) ( 17): in
effetti, però, abbiamo un po’ semplificato, in quanto sappiamo che l’accesso al bus
indirizzi può avvenire solo tramite il registro MAR; di conseguenza, la
microistruzione PC → M[SP] equivale in realtà a tre microistruzioni:
17
Nel caso in cui l’architettura del calcolatore non preveda un registro SP, è possibile usare un qualsiasi registro che ne
faccia le veci, purché ovviamente la sua lunghezza sia non inferiore alla lunghezza degli indirizzi di memoria.
Autore: Sandro Petrizzelli
34
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
PC → MDR
SP → MAR
MDR → M[MAR]
Una volta inserito nello stack l’indirizzo di rientro, il registro SP viene
chiaramente aggiornato (SP + L PC → SP) in modo da tener conto di questo
inserimento e puntare nuovamente alla cima dello stack. Solo a questo punto è
possibile eseguire materialmente il salto, ossia inserire nel registro PC l’indirizzo (α)
della prima istruzione della subroutine chiamata. L’ultima microistruzione abilita
infine il fetch dell’istruzione puntata dal registro PC.
Come si è detto prima, l’ultima istruzione della subroutine chiamata dovrà essere
una RTS (solo codice operativo, niente operandi), che equivarrà al seguente
microprogramma:
PC+L (IR) → PC
SP - L PC → SP
SP → MAR
M[MAR] → MDR
MDR → PC
0 → µPC
Ancora una volta, la prima istruzione è inutile, ma valgono le considerazioni già
fatte in precedenza. L’istruzione successiva fa in modo che il registro SP non punti
più alla cima dello stack (cioè alla prima locazione libera), ma all’elemento in cima
allo stack, che corrisponde senz’altro all’indirizzo di rientro dalla subroutine: tale
indirizzo, secondo le “modalità classiche” di accesso alla memoria, viene trasferito
nell’MDR e da questo nel registro PC.
C
nee
uzziioon
ntteerrrru
daa iin
paarrttiiccoollaarree:: ssaallttoo d
Caassoo p
Le interruzioni (interrupt) saranno ampiamente descritte nella sezione
successiva di questo capitolo. Per il momento, ci limitiamo a farne una analisi
semplice, al fine di ricollegarci ai discorsi degli ultimi paragrafi e vedere, a livello di
microprogrammazione, come vengono gestiti i passaggi ai programmi che si
occupano della gestione di tali interruzioni.
Le interruzioni sono sostanzialmente dei cambiamenti nel flusso di controllo
durante l'esecuzione del programma: essi possono dipendere sia dall'esecuzione del
programma sia da eventi “esterni” al programma, come ad esempio dei
malfunzionamenti oppure dei “segnali” inviati dalle periferiche di Input ed Output.
Il verificarsi di un interrupt ferma il programma in corso e trasferisce il controllo ad
un cosiddetto gestore di interrupt: si tratta semplicemente di una procedura che
esegue le azioni appropriate per l’interrupt e, quando ha finito, restituisce il
controllo al programma interrotto. In particolare, il programma deve riprendere
dall'esatto punto in cui era stato sospeso, il che significa che è necessario
ripristinare tutti i registri interni allo stato di pre-interruzione. Quest’ultimo è un
concetto chiave per la gestione degli interrupt ed è proprio quello che vogliamo
discutere adesso.
Supponiamo che un dato “evento” coinvolga una data periferica di I/O e questa
necessiti dell’attenzione da parte della CPU; allora, deve inviare un interrupt alla
CPU, utilizzando una apposita linea a livello hardware (linea INT): oltre ad asserire
il segnale su tale linea, la periferica invia anche un particolare codice (detto
aggiornamento: 10 luglio 2001
35
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
vettore di interrupt) che consente sostanzialmente alla CPU di individuare la
subroutine opportuna tramite cui servire l’interruzione. La struttura generale di
questa subroutine sarà del tipo seguente:
SUB xxx:
INT DISABLE
SAVE REGISTERS
………
………
………
RTI
Le prime due istruzioni sono importanti: la prima disabilita la linea degli
interrupt, in modo che l’esecuzione della subroutine non possa essere interrotta da
un altro interrupt ( 18); la seconda provvede al salvataggio di tutti i registri interni
della CPU (in particolare il registro che “contiene” lo stato della macchina, chiamato
PSW, Program Status Word), in modo che, quando la subroutine sarà terminata, si
potrà ripristinare il normale flusso del programma a partire dall’esatto stato in cui
era stato sospeso. Le successive istruzioni sono quelle specifiche per l’interruzione
che si è verificata e quindi non le approfondiamo. L’ultima istruzione è RTI (Return
From Interrupt): è una istruzione di salto incondizionato, che serve appunto a
riprendere l’esecuzione del programma dal punto in cui era stato interrotto.
Il microprogramma corrispondente all’istruzione RTI è del tipo seguente:
PC+L (IR) → PC
SP - L PSW → SP
M[SP] → PSW
SP-L PC → SP
M[SP] → PC
0 → µPC
Notiamo subito che le due microistruzioni M[SP]→PSW e M[SP]→PC sono in realtà
rappresentative ciascuna delle solite tre microistruzioni che coinvolgono i registri
MAR e MDR per l’accesso alla memoria, ma abbiamo preferito sintetizzare per non
complicare troppo il microprogramma.
Esaminando i dettagli di tale microprogramma, osserviamo che la prima
istruzione (PC+L (IR) →PC) è ancora una volta inutile ma comunque inevitabile. Le
successive due istruzioni presuppongono che il registro PSW della macchina sia
stato salvato in cima allo stack prima di servire l’interruzione, per cui viene
ricaricato, in modo da ripristinare lo stato della macchina al valore che aveva prima
dell’interruzione. Le successive due istruzioni ripetono la stessa operazione con il
registro PC, in modo da esso venga ripristinato con l’indirizzo dell’istruzione
successiva a quella che ha comportato l’interruzione (ma potrebbe anche trattarsi,
in alcuni casi, della stessa istruzione che ha causato l’interruzione e che deve
essere rieseguiti).
18
E’ in effetti una semplificazione, ma, se non vengono presi gli opportuni accorgimenti (ad esempio dei meccanismi di
priorità di cui parleremo in seguito), eventuali successive richieste di interrupt porterebbero la macchina in un loop
pauroso!
Autore: Sandro Petrizzelli
36
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
L
Lee iin
ntteerrrru
uzziio
on
nii
Introduzione
Abbiamo detto che il controllo è la parte più difficile del progetto di un
processore; in particolare, ciò che crea le maggiori difficoltà nel controllo sono le
cosiddetti interruzioni: si tratta di eventi distinti dalle diramazioni, ma che, come
esse, alterano il normale flusso di esecuzione delle istruzioni.
Inizialmente, le interruzioni furono inventate per segnalare errori aritmetici (ad
esempio l’overflow) oppure il verificarsi di eventi in tempo reale. Con il passare del
tempo, però, le interruzioni hanno risolto una grande quantità di compiti difficili.
Segue un elenco di alcuni tipici casi in cui si manifestano le interruzioni:
•
richiesta di I/O da parte di una periferica (caso tipico);
•
richiesta di un servizio del sistema operativo da parte di un programma
utente;
•
attività di trace delle istruzioni (si tratta cioè di analizzare passo dopo passo
come vengono eseguite le istruzioni);
•
inserzioni di punti di controllo (si tratta di interruzioni espressamente
previste dal programmatore, al fine di ottenere un ausilio per il debug dei
propri programmi);
•
overflow e underflow aritmetico;
•
mancanza di una pagina in memoria;
•
accesso disallineato alla memoria;
•
infrazione alle protezioni della memoria (dato che esistono parti della
memoria “protette”, riservate ad esempio dal sistema operativo per l’I/O e
quindi non accessibili da altri programmi);
•
uso di una istruzione indefinita (in questi casi, si può servire l’interruzione o
far in modo che venga eseguita una NOP, cioè Not Operation, senza
provocare interruzioni) ( 19);
•
malfunzionamenti dell’hardware;
•
caduta della tensione di alimentazione (in presenza di un simile evento, nel
caso se ne abbia il tempo ed il processore sia sufficientemente veloce,
possiamo provare a salvare il possibile sull’hard disk prima che la macchina
cessi di funzionare).
19
Una istruzione non definita corrisponde ad una istruzione il cui codice operativo risulti sconosciuto al processore: una
simile situazione si verifica a causa di errori nel compilatore oppure di guasti nella cache istruzioni o nel bus presente tra
tale cache ed il registro IR; si verificano altre in alcune CPU appartenenti a “famiglie” in cui le singole realizzazione
possono essere dotate, ad esempio, di unità aritmetiche sofisticate oppure non disporne e dover eseguire le corrispondenti
operazioni mediante routine software.
aggiornamento: 10 luglio 2001
37
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
T
Teerrm
miin
noollooggiiaa d
deellllee iin
ntteerrrru
uzziioon
nii
Col passare del tempo, i vari produttori di calcolatori hanno definito in modi
sempre diversi i vari eventi di interruzione, determinando spesso confusione. Ad
esempio, consideriamo una richiesta di I/O da parte di un dispositivo, ossia un caso
tipico di interruzione inviata alla CPU: nell’ IBM 360, tale evento viene definito
interruzione di ingresso/uscita, nel VAX viene definito interruzione da una periferica,
nel Motorola 680x0 viene definito eccezione e nell’Intel 80x86 viene definito
interruzione vettorizzata. In generale, tramite questo e tanti altri esempi, si nota che
Intel e IBM usano tuttora il termine interruzioni (interrupt), mentre invece Motorola
parla di eccezioni (exception); DEC, poi, usa vari termini, a seconda del tipo di
evento: eccezioni, guasti (fault), aborti (abort), trappole (trap) o interruzioni.
Concetti generali sulle interruzioni
Come già anticipato, gli interrupt sono cambiamenti nel flusso di controllo
durante l'esecuzione del programma: essi possono dipendere dall'esecuzione del
programma ma anche da qualcos'altro; ad esempio, un programma può “istruire” il
disco (tramite il suo controllore) per avviare il trasferimento di informazioni in
memoria, indicando al disco stesso di fornire un interrupt non appena il
trasferimento sarà finito.
Il verificarsi di un interrupt ferma il programma in corso e trasferisce il controllo
ad un cosiddetto gestore di interrupt: si tratta semplicemente di una procedura
che esegue le azioni appropriate per l’interrupt e, quando ha finito, restituisce il
controllo al programma interrotto. In particolare, il programma deve riprendere
dall'esatto punto in cui era stato sospeso, il che significa che è necessario
ripristinare tutti i registri interni allo stato di pre-interruzione. Quest’ultimo è un
concetto chiave per la gestione degli interrupt, per cui merita di essere
approfondito.
I moderni calcolatori sono sotto il controllo di un insieme gerarchico di
programmi, alla cui sommità c'è il sistema operativo. Per poter trasferire il
controllo del calcolatore da un certo programma ad un altro (a seconda della
operazione che si intende fare), è necessario fornire un meccanismo mediante il
quale il funzionamento di un programma possa essere temporaneamente
"interrotto" a favore di un altro:
•
una interruzione, come si detto poco fa, è un evento oppure un preciso
segnale esterno, che provoca appunto la sospensione del funzionamento di
un programma;
•
quando si verifica una interruzione, il contenuto di tutti i registri viene
immediatamente salvato e il controllo del calcolatore passa ad una routine
di servizio di interruzione, facente parte, in genere, del sistema
operativo: essa determina da dove viene la richiesta di interruzione
(generalmente esaminando un certo numero di flag o indicatori) e quindi
trasferisce a sua volta il controllo ad una routine per la gestione di quel
particolare tipo di interruzione;
•
successivamente, il funzionamento del programma viene ripreso
esattamente dal punto in cui era stato sospeso: questo lo si ottiene
facilmente inserendo in tutti i registri i valori che erano stati salvati al
momento della interruzione.
Autore: Sandro Petrizzelli
38
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Ogni interrupt specifico è identificato da un numero che ne indica il tipo: tale
numero prende il nome di vettore di interrupt (o anche semplicemente codice
interruzione). Per ogni interrupt, come si è detto, è previsto un programma gestore
di interrupt, che esegue l'intero lavoro richiesto dall'interrupt. L'indirizzo del gestore
di interrupt è contenuto a sua volta nella cosiddetta tabella degli interrupt, che
viene indicizzata dal vettore di interrupt.
Classificazione degli eventi
eventi che determinano interruzioni
E’ possibile, oltre che opportuno, classificare nel modo seguente gli eventi che
determinano le interruzioni:
•
eventi sincroni o eventi asincroni (rispetto al programma): si parla di
evento sincrono quando esso si verifica nello stesso punto ogni volta che il
programma viene eseguito con gli stessi dati e la medesima allocazione di
memoria; si parla invece di evento asincrono quando la sua occorrenza
dipende da numerosi fattori oltre al programma, ai suoi dati ed alla
memoria allocata, per cui esso non necessariamente si presenta negli stessi
punti. Ad esempio, sono eventi asincroni i malfunzionamenti hardware: in
generale, comunque, tutti gli eventi asincroni sono causati da dispositivi
esterni alla memoria ed al processore;
•
eventi richiesti dall’utente o imposizioni esterne: un evento è richiesto
dall’utente quando egli lo sollecita direttamente, mentre invece si parla di
imposizione esterna quando l’evento è determinato da altri fattori diversi
dalla volontà del programmatore;
•
eventi mascherabili o non mascherabili dall’utente: si dice che un evento
è mascherabile se può essere disabilitato da un programma utente, nel
senso che l’esecuzione può proseguire senza servire l’interruzione (cioè
ignorandola, temporaneamente o definitivamente); se invece l’evento deve
necessariamente essere “gestito”, allora si parla di evento non mascherabile;
•
eventi interni ed esterni alle istruzioni: un evento è detto interno ad una
istruzione se può impedire il completamento della sua esecuzione, mentre
invece è detto esterno se, prima di servirlo, si può completare l’esecuzione
dell’istruzione;
•
possibilità di ripresa dell’esecuzione o necessità di terminazione:
quando una interruzione determina necessariamente la terminazione
dell’intero programma in corso di esecuzione, si parla di evento terminale (o
anche non ripristinabile); se invece l’evento, pur imponendo la sospensione
dell’esecuzione del programma, consente comunque la ripresa di tale
esecuzione, allora si parla di evento ripristinabile.
Al fine di chiarire ulteriormente quanto appena detto, la seguente tabella riporta
alcuni eventi tipici e la loro classificazione dal punto di vista degli aspetti classificati
poco fa. In particolare, gli esempi riportati fanno tutti riferimento a chiamate al
sistema operativo ( 20):
20
Si tenga presente che, quando “lanciamo” l’esecuzione di un programma, quest’ultimo “gira” sotto il sistema operativo,
ossia come sottoprogramma del sistema operativo.
aggiornamento: 10 luglio 2001
39
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Evento
Sincrono o
asincrono
Richiesta da
utente o
automatico
Mascherabile o
non
mascherabile
dall’utente
Interno o esterno
alle istruzioni
Ripristinabile o
non ripristinabile
Asincrono
Automatico
Non mascherabile
Esterno
Ripristinabile
Sincrono
Richiesta da utente
Mascherabile
Esterno
Ripristinabile
Sincrono
Richiesta da Utente
Mascherabile
Esterno
Ripristinabile
Sincrono
Automatico
Mascherabile
Interno
Non ripristinabile
Sincrono
Automatico
Mascherabile
Interno
Ripristinabile
Sincrono
Automatico
Non mascherabile
Interno
Ripristinabile
Sincrono
Automatico
Mascherabile
Interno
Non ripristinabile
Sincrono
Automatico
Non mascherabile
Interno
Non ripristinabile
Sincrono
Automatico
Non mascherabile
Interno
Non ripristinabile
Asincrono
Automatico
Non mascherabile
Interno
Non ripristinabile
Asincrono
Automatico
Non mascherabile
Interno
Non ripristinabile
Sincrono
Richiesta da utente
Non mascherabile
Esterno
Ripristinabile
Richiesta di una
periferica di I/O
Esecuzione passopasso delle istruzioni
Punto di controllo
(break point)
Overflow in aritmetica
binaria
Overflow o underflow in
virgola mobile
Mancanza di pagina
Accesso disallineato
alla memoria
Violazione dei diritti di
accesso alla memoria
Uso di istruzioni non
definite
Guasto fisico
Caduta di tensione
Altre chiamate al
sistema operativo
E’ intuitivo comprendere che le situazioni più difficili sono quelle in cui le
interruzioni si verificano “all’interno delle istruzioni”, ossia mentre è in corso una
delle fasi di esecuzione delle singole istruzioni: come si è già detto e come
approfondiremo anche in seguito, in alcuni casi (quelli più complessi) l’interruzione
deve essere immediatamente servita, mentre invece in altri si può comunque
aspettare che l’esecuzione dell’istruzione venga completata;
Nella tabella spicca il fatto che la maggior parte delle interruzioni sono di tipo
sincrono e sono in qualche modo relative al software, ossia legate all’esecuzione delle
istruzioni ed agli eventi che tale esecuzione determina. La maggior parte delle
interruzioni asincrone sono invece legate all’hardware, come i guasti fisici o le cadute
di tensione.
Si nota anche che tutte le interruzioni avvengono non per volontà del
programmatore, tranne quelle da egli espressamente previste.
Si evidenzia invece un sostanziale bilancio tra le interruzioni mascherabili e quelle
non mascherabili, così come tra quelle ripristinabili e quelle non ripristinabili.
Una cosa interessante da notare è il fatto che l’interruzione dovuta ad overflow,
sia in aritmetica intera sia in virgola mobile, è ritenuta mascherabile: questo perché
se si decidesse di realizzare un divisore che, in presenza di denominatore nullo, deve
fornire il massimo valore rappresentabile in una word, potremmo scegliere di
proseguire senza arrestare il funzionamento. Bisogna infatti tener conto che
l’overflow non è altro che la presenza di un riporto sul bit del segno: nel caso
dell’aritmetica binaria, potrebbe essere che si stanno usando numeri non segnati,
per cui tale riporto non crea alcun problema; nel caso di virgola mobile, invece,
potrebbe essere sufficiente far stampare un messaggio a video e proseguire
ugualmente ( 21).
Altra cosa interessante è invece quella per cui una interruzione dovuta ad un
accesso disallineato a memoria è ritenuta non ripristinabile: in generale, in
21
A tal proposito, sottolineiamo che l’eventuale stampa del messaggio di errore avviene a cura della routine di gestione
dell’interrupt.
Autore: Sandro Petrizzelli
40
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
presenza di un accesso disallineato a memoria, dobbiamo saltare ad una subroutine
di disallineamento, la quale deve leggere le due parole di memoria (consecutive)
contenenti la parola disallineata di interesse, spostarle nella CPU e infine comporle
per ottenere la parola di interesse. Ricordiamo che gli accessi disallineati a memoria
si possono avere solo su macchine che usano indirizzamento della memoria
superiore al byte (ad esempio, indirizzamento alle parole): in questo tipo di
macchine, è sempre opportuno prevedere le interruzioni dovute al disallineamento.
E’ bene inoltre sottolineare che un accesso disallineato a memoria riguarda sempre
l’accesso ai dati contenuti nella memoria e non l’accesso alle istruzioni, visto che il
registro PC viene regolarmente incrementato di 4 byte.
Per concludere, segnaliamo che, per le applicazioni particolarmente delicate
(pensiamo ai sistemi per transazioni economiche), esistono delle particolari CPU
cosiddette fault-tolerant, ossia tolleranti al guasto: si tratta di CPU multiprocessore,
che ospitano più processori identici su uno stesso chip; quando un processore si
accorge che un altro processore sta avendo dei problemi di difficile o impossibile
risoluzione, è in grado di sostituirlo (processo killer).
Rilevazione delle interruzioni da parte del controllo
Abbiamo detto in precedenza che l’attività di controllo in un calcolatore è
comodamente rappresentabile tramite un diagramma a stati finiti, in cui ogni stato
corrisponde a tutte le operazioni che si possono compiere in uno stesso ciclo di clock
ed in cui le transizioni di stato sono determinate dall’accadere di determinati eventi,
non ultime le interruzioni. In particolare, abbiamo in precedenza considerato il
seguente esempio di diagramma a stati finiti, in cui sono riportati gli stati e le
relative operazioni relative alle prime due fasi di esecuzione di una istruzione, vale a
dire prelevamento dalla memoria e decodifica (con preparazione degli operandi):
MAR ç PC
Accesso alla
memoria
incompleto
IR ç M[MAR]
Accesso alla
memoria completo
PC ç PC + 4
A ç Rs1
B ç Rs2
L’ultimo stato presenta evidentemente una diramazione, in quanto le successive
operazioni da compiere dipendono dal tipo di istruzione (ad esempio una istruzione
aritmetica di somma o di differenza oppure una istruzione di caricamento di un dato
dalla memoria oppure di memorizzazione di un dato nella memoria).
Quel diagramma deve essere necessariamente modificato se si vuole integrare il
controllo con il riconoscimento e la gestione delle interruzioni. A tal fine, dobbiamo
tener conto della possibilità che l’eventuale interruzione sia esterna o interna
all’istruzione: nel primo caso, si può attendere il completamente dell’esecuzione
aggiornamento: 10 luglio 2001
41
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
dell’istruzione prima di servire l’interruzione, mentre invece, in caso contrario,
l’interruzione deve essere servita immediatamente e quindi l’esecuzione
dell’istruzione deve essere sospesa e ripristinata successivamente.
Consideriamo la fase di caricamento dell’istruzione nel registro IR, corrispondente
al secondo stato nel diagramma prima riportato:
•
in assenza di qualunque interruzione, il ciclo di caricamento viene ripetuto
(accesso alla memoria incompleto) fino a quando l’intera istruzione si trova
in IR (accesso alla memoria completo);
•
in presenza di interruzione, invece, il comportamento del controllo dipende
dal tipo di interruzione:
•
se l’interruzione è interna, come ad esempio la mancanza, in memoria
principale, della pagina contenente l’istruzione da caricare o parte di essa
( 22), non si può fare a meno di servire l’interruzione prima di decodificare
l’istruzione stessa;
•
se invece l’interruzione è esterna, si può procedere con l’esecuzione e fare in
modo che l’interruzione risulti pendente, in modo da servirla prima di
passare all’esecuzione dell’istruzione successiva.
Il seguente diagramma di flusso descrive il meccanismo adottato:
Ritorno da interruzione
Interruzione
pendente
IAR ç PC
MAR ç PC
IR ç M[MAR]
Mancanza
di pagina
PC ç indirizzo del gestore dell'interruzione;
Accesso alla memoria
incompleto
(nessuna interruzione)
Accesso alla
memoria completo
(nessuna interruzione)
interruzione ripristinata
PC ç PC + 4
A ç Rs1
B ç Rs2
Nel momento in cui si decide di servire l’interruzione, il valore del PC viene
registrato nel registro IAR (indirizzo di ritorno dalle interruzioni) e, al ciclo di clock
successivo, il PC viene caricato con l’indirizzo del gestore dell’interruzione e quindi
tale gestore può essere eseguito. Fatto questo, l’interruzione può essere ripristinata,
nel senso che il contenuto originale del PC (cioè quello prima della chiamata
dell’istruzione) viene riportato appunto in PC e l’esecuzione dell’istruzione ripresa dal
punto in cui era stata interrotta.
Quella appena descritta non è però l’unica situazione possibile. Infatti, una
interruzione potrebbe verificarsi anche nella terza fase di esecuzione dell’istruzione,
ad esempio quando il risultato di una somma determina un overflow oppure quando,
22
Questo concetto sarà chiarito successivamente, quando si parlerà di memoria virtuale. In generale, diciamo che si tratta
di quella situazione in cui i dati cui si è interessati non si trovano nella memoria principale ma su una memoria secondaria,
per cui devono essere prima caricati nella memoria principale e poi da questa all’interno della CPU.
Autore: Sandro Petrizzelli
42
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
per un caricamento di una dato dalla memoria, si verifica nuovamente una
mancanza di pagina. In questo secondo caso, per esempio, il diagramma di prima si
modifica nel modo seguente:
Ritorno da interruzione
Interruzione
pendente
IAR ç PC
MAR ç PC
Accesso alla memoria
incompleto
(nessuna interruzione)
IR ç M[MAR]
Mancanza
di pagina
PC ç indirizzo del gestore dell'interruzione;
Accesso alla
memoria completo
(nessuna interruzione)
interruzione ripristinata
PC ç PC + 4
A ç Rs1
B ç Rs2
Mancanza
di pagina
Accesso alla memoria
incompleto
(nessuna interruzione)
MDR ç M[MAR]
Accesso alla
memoria completo
(nessuna interruzione)
Interruzione in caso di mancanza di pagina per una operazione di caricamento dati in memoria
Situazione del tutto analoga si ha quando l’istruzione in corso di esecuzione
prevede una somma:
Ritorno da interruzione
Interruzione
pendente
IAR ç PC
MAR ç PC
Accesso alla memoria
incompleto
(nessuna interruzione)
IR ç M[MAR]
Mancanza
di pagina
PC ç indirizzo del gestore dell'interruzione;
Accesso alla
memoria completo
(nessuna interruzione)
interruzione ripristinata
PC ç PC + 4
A ç Rs1
B ç Rs2
Overflow
C ç A+Temp
Nessun overflow
Interruzione in caso di overflow per una operazione di somma
aggiornamento: 10 luglio 2001
43
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Capitolo 5
Da notare che le istruzioni che possono provocare una interruzione, come
appunto quelle di somma, si occupano loro stesse della verifica di interruzione.
Il controllo per il DLX
Il seguente diagramma a stati finiti rappresenta il controllo dedicato per il DLX:
Ritorno da interruzione
MAR ç PC
IR ç M[MAR]
IAR ç PC
Accesso alla memoria
incompleto
(nessuna interruzione)
Accesso alla
memoria completo
(nessuna interruzione)
PC ç 0
PC ç PC + 4
A ç Rs1
B ç Rs2
ripristino interruzione
Trasferimento
dati
ALU
Inizializzazione
Salto
Diramazione
Dato che i particolari da evidenziare in diagrammi come questo sono davvero tanti,
si ritiene poco opportuno disegnare la macchina a stati finiti DLX in un’unica figura
contenenti tutti i 52 stati: di conseguenza, la figura appena riportata mostra solo il
livello più alto del controllo, che contiene cinque stati ed i riferimenti agli stati
rimanenti; questi ultimi sono dettagliatamente descritti nelle figure da 5.10 (pag.
187) a 5.14 (pag. 189) del testo.
La figura appena riportata mostra dunque i primi due stati dell’esecuzione delle
istruzioni: il prelievo dell’istruzione dalla memoria, all’indirizzo puntato da PC, e la
decodifica dell’istruzione. Il primo stato viene ripetuto fin quando l’intera istruzione
non viene prelevata dalla memoria o si verifica una interruzione. Nel caso in cui tale
interruzione venga rilevata, il contenuto del PC viene salvato in IAR e poi PC viene
aggiornato all’indirizzo della routine di servizio delle interruzioni: in particolare, la
figura riporta un indirizzo puramente simbolico pari a 0, che è appunto da intendersi
come quello della routine di servizio delle interruzioni ( 23).
23
L’indirizzo 0, pur essendo solo simbolico nella figura riportata, nasconde in realtà un altro significato: quando stiamo
lavorando sul nostro calcolatore e, per un qualunque motivo, ne chiediamo il reset (ad esempio spingendo l’apposito
pulsante sul pannello frontale), automaticamente il registro PC viene caricato con l’indirizzo 0, che individua la prima
istruzione situata nella ROM del calcolatore: tale istruzione corrisponde al kernel del computer, che si occupa di compiere
delle verifiche sulle periferiche installate nonché sulla quantità di memoria disponibile; in particolare, il “test” sulla
memoria avviene tramite un ciclo di scrittura/lettura: vengono prima scritti tutti i byte a 0 e poi vengono letti controllando
Autore: Sandro Petrizzelli
44
aggiornamento: 10 luglio 2001
Tecniche di base per la realizzazione dei processori
Gli ultimi tre stadi dell’esecuzione delle istruzioni, vale a dire esecuzione e calcolo
degli indirizzi, accesso alla memoria e scrittura del risultato, sono mostrati nelle
figure da 5.10 (pag. 187) a 5.14 (pag. 189) del testo, di cui si consiglia la lettura.
Autore: Sandro Petrizzelli
e-mail: [email protected]
sito personale: http://users.iol.it/sandry
che siano tutti a 0; successivamente, tutti i byte vengono scritti a 1 e vengono poi letti controllando che sia tutti 1.
L’eventuale rilevazione di anche un solo byte danneggiato comporta l’arresto della macchina, dato che nella memoria
RAM non sono tollerate “interruzioni” di nessun tipo. Da notare che questo vincolo così rigido non esiste invece per il
disco rigido, dove una eventuale parola risultata danneggiata viene semplicemente contrassegnata come tale e ritenuta non
più disponibile, senza che però il disco intero vada riparato o, peggio, buttato.
aggiornamento: 10 luglio 2001
45
Autore: Sandro Petrizzelli