Sei sulla pagina 1di 26

CAPITOLO 4

In questo capitolo si illustrerà il progetto dell'unità di elaborazione dati e di controllo. Non si


progetterà un sistema di controllo completo, ma solamente un sottoinsieme limitato che però
sarà sufficiente ad illustrare i principi basilari! Questo insieme limitato che esclude i numeri in
virgola mobile comprende:

• Istruzioni di accesso alla memoria load e store word

• Istruzioni logico-aritmentiche add, sub, and, or e slt

• Istruzioni di salto branch equal beq e jump j

Le unità funzionali implementate nel MIPS sono di due tipi. Abbiamo le reti combinatorie le
quali uscite dipendono solamente dagli ingressi. Questo significa che con ingressi sempre uguali
si avranno uscite sempre uguali. Abbiamo poi le reti sequenziali, composte da elementi di
stato. Questi elementi di stato sono vere e proprie memorie che appunto memorizzano uno
stato. Di conseguenza l'uscita di una rete sequenziale non è solamente data dal tipo di ingresso,
ma anche dal dato registrato nell'elemento di stato! Questo elemento lo abbiamo già trovato e
comprende due ingressi ed una uscita. Un esempio è un flip-flop D con un ingresso di dato e
l'altro di clock.

Altro fattore importante è la metodologia di temporizzazione che definisce quando i dati


possono essere scritti o letti. Questo fattore è determinante in quanto una temporizzazione
errata potrebbe ad esempio far leggere un dato nel momento della scrittura, perdendo di
conseguenza il dato che ci interessava, registrato al colpo di clock precedente! Una
metodologia di temporizzazione è quella chiamata edge-triggered, in italiano sensibile ai
fronti. Il clock non è altro che un segnale elettrico, alto o basso! Possiamo prendere in
considerazione solo quando è alto o è basso. Nel nostro caso invece, nella temporizzazione
sensibile ai fronti, questa situazione è addirittura indifferente. A noi interessa solamente
quando il clock si trova nel periodo di cambiamento tra alto e basso e viceversa. Questi
momenti sono appunto chiamati fronti. Un segnale affermato equivale ad un segnale vero, un
segnale da affermare è un segnale che deve essere posto ad un valore vero!

Nelle figure che vedremo i segnali maggiori di 1bit saranno evidenziati in quanto bus. In ogni
caso la maggior parte degli elementi sarà di 32bit in quanto il sistema stesso è basato su questo
numero di bit. I segnali di controllo saranno poi di colore differente da quelli dei dati.
Progetto dell'Unità di Elaborazione Dati
Per progettare una unità di elaborazione dati è cosa giusta elencare gli elementi da
implementare. Si può quindi procedere analizzando le
istruzioni e da queste dedurre il frammento di unità di
elaborazione adibito al controllo di questa istruzione!
Man mano che si va avanti la nostra unità crescerà di
conseguenza e sarà sempre più completa. Quindi
procediamo a pezzi.

Il primo elemento necessario è un componente in cui


memorizzare le istruzioni del programma! Abbiamo
bisogno di due registri, uno che memorizzi l'istruzione,
l'altro che memorizzi l'indirizzo dell'istruzione!
Quest'ultimo deve essere poi associato ad un terzo
elemento che dovrebbe essere già noto col nome di
contatore di programma (program counter - PC). Al PC
dobbiamo poi affiancare un sommatore che lo incrementi per ottenere l'indirizzo dell'istruzione
successiva! Questo sommatore non è altro che una ALU particolare che faccia solamente la
somma PC + 4 (in quanto le istruzioni tra loro si trovano ogni 4byte)! La chiameremo per questo
Somma.

Ad ogni ciclo di clock il PC viene riscritto, causando il cambiamento dell'indirizzo di lettura! In


poche parole ad ogni cambiamenti del PC viene associata una nuova istruzione che esce dalla
memoria delle istruzioni per essere eseguita.

Adesso prendiamo in esame,


come secondo elemento, le
istruzioni formato-R che come
sappiamo sono quelle logico-
aritmetiche! Ogni istruzione si
riferisce a tre registri di memoria
(rs,rt,rd) dove sono memorizzati i
dati da analizzare e dove devo
scrivere il dato analizzato. Questi
registri, che sono elementi di
stato, sono inclusi in una
struttura che prende in nome di
register-file. Questa struttura è in generale l'elemento che contiene lo stato dei registri della
macchina e richiede una ALU per effettuare le sue operazioni!

Gli ingressi delle istruzioni formato-R sono due (quindi due dati da leggere) e l'uscita una sola
(un dato da scrivere)! In generale per scrivere un dato serve il dato e il numero di registro. Ma
quest'ultimo è già di per se specificato in uno dei tre ingressi dell'istruzione! Gli altri due
registri sono anch'essi specificati nel segnale Istruzione della figura precedente.

Come vediamo al segnale istruzione appartengono i due indirizzi di registro (Registro letto 1 e
2) per i dati in ingresso e l'indirizzo del registro (Registro scritto) del dato da scrivere! Il dato da
scrivere è il risultato della ALU (Dato scritto). Tutto quanto è poi gestito da un segnale di clock
RegWrite. Come anticipato inoltre la nostra figura possiede diversi colori a seconda che si tratti
di segnali di dato o di controllo e i bus sono segnati in grassetto. Tutti i dati gestiti da questo
circuito sono difatti a 32bit!

Il nostro terzo elemento sarà un circuito che implementi le operazioni di load e store word.
L'istruzione ad esempio è: lw $t0, offset($t1) o sw $t0, offset($t1). Cosa ci serve? Prima di tutto
un register-file e poi una ALU che permetta di fare la somma dell'offset e $t1. L'offset deve
essere inoltre prima esteso a 32bit (in quanto di natura è di 16bit) con la procedura spiegata
nei capitoli precedenti.

Nel caso del load word, il dato verrà prelevato dalla memoria dati e inviato al register-file
(Dato letto in Memoria a Dato scritto nel Registri), nel caso dello store word il dato sarà
trasferito dal register-file alla memoria dati (Dato letto 2 nel Registri a Dato scritto nella
Memoria).

Il nostro quarto ed ultimo elemento è quello che implementa il beq e il jump. Il beq ha tre
operandi: due registri da confrontare e l'offset per l'indirizzo dell'istruzione che deve essere
sommato a partire dall'indirizzo di stato dell'istruzione beq (esempio: beq $t0, $t1, offset). Per
effettuare tutto ciò cosa serve? In primis un'estensione dell'offset e poi la somma dell'offset
esteso al PC in quel dato momento per calcolare l'indirizzo di destinazione. Prima di proseguire
dobbiamo attenerci anche a due fattori importanti:
• L'ISA specifica che il calcolo dell'indirizzo di salto (il destinatario) è l'indirizzo
dell'istruzione che segue quella di salto. Infatti già la struttura del circuito calcola PC +
4 di proprio... noi per semplificarci la vita partiremo da questo dato e non dal PC
solamente!

• Scalare il campo offset di due bit a sinistra

Una volta fatto tutto ciò, si calcola l'istruzione. Si procede sottraendo i due numeri e
verificando il risultato se è o non è pari a 0 (zero). Nel caso i due operandi fossero uguali il
salto è da effettuare registrando l'indirizzo appena calcolato nel PC. Nel caso i due operando
fossero disuguali viene registrato in PC solamente il valore di PC + 4. In qust'ultimo modo il
salto ovviamente non è stato effettuato. A livello hardware l'unico cambiamento importante da
apportare all'unità di elaborazione fino ad adesso progettata è nel prelievo dal register-file.

Notiamo una ALU in più dedicata al calcolo dell'indirizzo di salto. Più qualche modifica o meglio
l'effettiva circuteria necessaria solamente al beq. Per il jump vedremo più avanti!
Un'implementazione elementare
Adesso fonderemo gli elementi appena studiati, con una unità di elaborazione elementare.
Faremo in modo anche di studiare il corretto funzionamento dei segnali di controllo!

Volendo fondere assieme gli elementi studiati precedentemente, ci accorgiamo che dobbiamo
implementare circuiti per un istruzione in particolare, ma allo stesso tempo molti altri circuiti
possono essere usati da più istruzioni! Di conseguenza sarà opportuno effettuare collegamenti
multipli ai vari elementi dell'unità di elaborazione, e aggiungere una sorta di filtro dati, ad
esempio un multiplexer, a seconda dell'istruzione che stiamo eseguendo! La nostra ALU
effettuerà un'istruzione ad ogni ciclo di clock!

Il secondo e terzo elemento della nostra unità sono molto simili e di conseguenza facili da
unire. Ecco lo schema finale:

A questo è immediata l'aggiunta del primo elemento. Ecco lo schema:


Infine anche il quarto è fondibile in modo semplice:

In quest'ultimo caso abbiamo aggiunto componenti prima non implementati. Abbiamo aggiunto
in particolare un multiplexer (in alto) gestito da un segnale PCsrc che seglie se inviare al
sommatore del PC il dato PC+4 o l'indirizzo di salto. Nel primo caso quando beq non risulta
essere vero, nel secondo quando beq risulta essere vero!

Adesso che abbiamo completato la nostra unità di elaborazione elementare, progettiamo l'unità
di controllo, quella cioè che gestisce i segnali di controllo! Prima di progettare l'unità di
controllo principale (quella dell'unità di elaborazione) è meglio progettare quella riferita alla
ALU solamente!

Gli ingressi di controllo per la ALU sono 3 e ci servono 5 sole combinazioni sulle 8 possibili.

Ingresso Funzione
000 AND
001 OR
010 Somma
110 Sottrazione
Set On Less
111
Than

Infatti per nei casi lw e sw l'unità di elaborazinoe calcola gli indirizzi con una somma, nei casi
delle istruzioni di tipo R effettua solo una delle cinque possibilità (a seconda del campo funct) e
per l'istruzione beq effettua una sottrazione! Come possiamo ben capire queste 5 funzioni
permettono di fare tutto il lavoro!
Come possiamo inviare questi 3bit di controllo? Dobbiamo creare un'apparato indipendente che
riceva il campo funct dell'istruzione e il campo di controllo a 2bit che chiameremo ALUop.
Quest'ultimo segnale indica se l'operazione da eseguire è una somma (00), una sottrazione (01)
o deve essere determinata dall'operazione codificata nel campo funt (10). La tabella qui di
seguito, riporta i valori da assegnare agli ingressi di controllo della ALU in funzione del segnale
ALUop e del funct!

Codice Ingresso di
ALUop Operazione Campo Funct Azione ALU
Operativo controllo
LW 00 load word ------ somma 010
SW 00 store word ------ somma 010
BEQ 01 branch equal ------ sottrazione 110
Tipo-R 10 somma 100000 somma 010
Tipo-R 10 sottrazione 100010 sottrazione 110
Tipo-R 10 AND 100100 and 000
Tipo-R 10 OR 100101 or 001
Set On Less set on less
Tipo-R 10 101010 111
Than than

Ma come possiamo dato un ingresso a 2bit (ALUop) ed uno a 6bit (funct) far uscire un codice
appropriato di 3bit? Si può fare creando un'apposita tabella delle varità da cui è possibile
ricavare un circuito composto da porte logiche! La tabella di seguito è la tabella delle verità
appena descritta. Non rappresenta ovviamente tutte le combinazioni possibili, perchè
risulterebbe enormemente più grande (2^8 = 256 elementi), ma solamente quelle di interesse.
Le varie X presenti sono valori in ingresso indifferenti rispetto all'uscita! In poche parole la X
indica che l'uscita non dipende dal valore dell'ingresso corrispondente!

Funct
ALUop1ALUop0 Funct4Funct3Funct2Funct1Funct0Uscita
5
0 0 x x x x x x 010
x 1 x x x x x x 110
1 x x x 0 0 0 0 010
1 x x x 0 0 1 0 110
1 x x x 0 1 0 0 000
1 x x x 0 1 0 1 001
1 x x x 1 0 1 0 111

Ecco il nostro schema con l'unità di controllo della ALU.


Adesso possiamo passare alla progrettazione dell'unità di controllo principale anche perchè
senza di questa il segnale ALUop non esiste! Per farlo analiziamo i formati delle possibili
istruzioni con le posizioni dei bit:

Tipo-R:

0 rs rt rd shamt funct
31-26 25-21 20-16 15-11 10-6 5-0

Istruzione load/store:

35 o 43 rs rd address
31-26 25-21 20-16 15-0

Istruzione salto condizionato:

4 rs rd address
31-26 25-21 20-16 15-0

Osservazioni:

• Il codice Op (opcode) è sempre contenuto nei bit 31-26. Si indicherà questo campo con
Op[5-0]

• I registri sono specificati nei campi rs e rt nelle posizioni 25-21 e 20-16

• Il registro base per load/store e beq è sempre nei bit 25-21 (rs)

• L'offset da 16bit per beq e load/store è sempre nelle posizioni 15-0


• Il registro di destinazione può essere in due posizioni: per load/store è 20-16 (rt), per
Tipo-R è 15-11 (rd). E' necessario quindi un multiplexer che scelga il numero di registro
da scrivere

Possiamo vedere queste specifiche nella figura precedente. Come ogni parte del campo
dell'istruzione venga inserito nel circuito dell'unità di elaborazione. Perchè è importante sapere
i formati delle istruzioni? Perchè l'unità di controllo basa il suo funzionamento direttamente sul
codice operativo presente nel campo Op dei vari formati! L'unità di controllo ha infatti un
ingresso da 6bit! Qui di seguito è rappresentata la tabella dei sette segnali di controllo con la
relativa spiegazione.

Nome del segnale Effetto quando non affermato Effetto quando affermato
Il numero del registro
Il numero del registro
destinazione per Registro
RegDst destinazione per Registro
scritto proviene dal campo
scritto proviene dal campo rt
rd
Nel registro specificato
dall'ingresso Registro
RegWrite Nessuno scritto è scritto il valore
presente sull'ingresso Dato
scritto
Il secondao operando della
Il secondo operando della ALU
ALU è la versione estesa di
ALUsrc proviene dalla seconda uscita
16bit inferiori
del register file
dell'istruzione
Il valore di PC viene Il valore PC viene
sostituito dall'uscita del sostituito dall'uscita del
PCsrc
sommatore che calcola il sommatore che calcola la
valore PC+4 destinazione del salto
Il contenuto della cella di
memoria dati determinato
MemRead Nessuno
dall'ingresso Indirizzo è
posto sull'uscita Dato letto
Il contenuto della cella di
memoria dati determinato
dall'ingresso Indirizzo è
MemWrite Nessuno
sotituito dal valore
presente sull'ingresso Dato
scritto
Il valore inviato
Il valore inviato all'ingresso
all'ingresso Dato scritto
MemtoReg Dato scritto dei registro
dei registro proviene dalla
proviene dalla ALU
memoria dati

Bisogna però precisare che l'unità di controllo gestisce direttamente tutti questi segnali tranne
uno: il PCsrc. Questo segnale è affermato se il beq e l'uscita Zero della ALU sono affermate. Per
generare questo segnale occorre quindi una porta AND a due ingressi che sono appunto lo Zero
della ALU e un segnale proveniente dall'unità di controllo che chiameremo branch. Non
scordiamoci poi il segnale ALUop anch'esso uscente dall'unità di controllo!
Infine ecco la tabella delle verità per la costruzione del circuito in dettaglio dell'unità di
controllo principale.

Istruzion MemtoRe RegWrit MemWrit


RegDst ALUsrc RegRead Branch ALUop1 ALUop0
e g e e
formato-R 1 0 0 1 0 0 0 1 0
lw 0 1 1 1 1 0 0 0 0
sw x 1 x 0 0 1 0 0 0
beq x 0 x 0 0 0 1 0 1
Implementazione dei salti incondizionati
Una classe di istruzione che abbiamo più volte citato, ma che è assente dalla nostra
implementazione è quella dei salti incondizionati. Analizziamo quindi il formato dell'istruzione
di jump.

2 address
31-26 25-0

Il jump è molto simile al beq con la differenza che l'indirizzo di destinazione si calcola in modo
differente, e soprattutto il salto è incondizionato. L'indirizzo di destinazione in questo caso si
ottiene concatenando i 4bit più significativi del PC+4, con il campo address da 26bit
aggiungendo 00(due) nei 2bit meno significativi. Ecco la nostra unità di elaborazione espansa:
Implementazioni multi-ciclo
Sebbene il nostro progetto funzioni, la modalità a singolo ciclo non viene usata a causa della
sua inefficienza. Infatti il ciclo di clock è uguale per ogni istruzione o meglio deve permettere
lo svolgersi completo di una istruzione! L'istruzione che richiede più tempo è sicuramente la
load/store che quindi stabilisce il tempo di clock standard! Ma questo ciclo di clock è perfetto
solo per questo tipo di istruzione, negli altri casi è troppo lento! Si ha quindi una perdita di
tempo oltre alla violazione di una regola base per lo sviluppo di hardware che prevede metodi
per rendere più veloce gli eventi più comuni... inoltre nell'implementazione a singolo ciclo ogni
componente può essere usato singolarmente durante il ciclo costringendo alla duplicazione di
certe parti e al rincaro del costo di produzione!

Ci sono tuttavia tecniche che permettono di risolvere questo problema uno è il multi-ciclo che
vedremo adesso. L'altro, nel prossimo capitolo, è la pipeline!

La modalità multi-ciclo prevede la suddivisione in passi delle varie istruzioni. Il ciclo di clock
non è relativo dunque all'istruzione totale ma al singolo passo. In questo modo poi l'unità
funzionale della nostra unità di elaborazione può essere condivisa dalla stessa istruzione più
volte purché in clock differenti! Ciò ci permette di ridurre la quantità di hardware e soprattutto
di avere:

• Una sola unità di memoria per istruzioni e dati

• Una sola ALU al posto di una ALU e due sommatori

• Uno o più registri aggiuntivi associati a ciascuna unità funzionale per memorizzare
l'uscita fino al suo utilizzo al clock successivo

Ad ogni ciclo di clock tutti i dati che verranno usati successivamente devono essere
memorizzati in un elemento di stato. In particolare se questo dato dovrà essere usato da
un'istruzione diversa allora verrà memorizzato in elementi di stato quali il register-file, PC o la
memoria. Nel caso invece questo dato debba essere utilizzato dalla stessa istruzione nel ciclo di
clock successivo, allora verrà registrato in uno dei registri aggiuntivi che andiamo a vedere
adesso. La posizione di questi registri aggiuntivi è determinata da due fattori: quali unità
funzionali riescono a lavorare in un singolo ciclo di clock e quali dati saranno richiesti nei cicli
successivi. Per darci una regola immaginiamo che in un singolo ciclo è possibile solamente
effettuare un accesso in memoria oppure al register-file oppure svolgere operazioni con la ALU.
Per soddisfare questi requisiti sono stati aggiunti questi registri:

• Registro dell'Istruzione (Instruction Register, IR) e il Registro dei Dati di Memoria


(Memory Data Register, MDR) che memorizzano le istruzioni e i dati uscenti dalla
memoria durante l'operazione di lettura!

• Registro A e B che memorizzano i valori dei registri letti dal register-file

• Registro ALUout che memorizza l'uscita della ALU

Ogni registro tranne l'IR memorizza dati nuovi ad ogni ciclo di clock e non richiede dunque un
segnale di controllo. L'IR invece lo richiede perché deve ovviamente aspettare la fine
dell'esecuzione dell'intera istruzione prima di poter memorizzarne un'altra.

Come abbiamo detto alcune unità funzionali sono state condivise. L'uso di una sola memoria per
dati e istruzioni necessita di un multiplexer che selezioni la provenienza degli indirizzi di
memoria tra la sergente PC e quella ALUout. L'uso di una sola ALU per tutto richiede un
multiplexer sul primo ingresso che selezioni il registro A o PC e l'espansione del multiplexer al
secondo ingresso da due a quattro vie, con l'aggiunta della costante 4 (del PC+4) e l'offset, già
esteso e calcolato! Le istruzioni beq e jump richiedono poi un'ulteriore implementazione per la
scelta del segnale proveniente dall'ALU per il PC+4, dal registro ALUout o dal registro IR in
questo caso scalando i 26bit meno significativi di 2bit a sinistra e concatenando i 4bit superiori
del PC+4 (per l'istruzione jump). Per il controllo sulla scrittura del PC servono due segnali di
controllo in più che chiameremo PCwrite e PCwriteCond. Nel caso dei salti condizionati per
determinare se PC debba essere scritto, si pone in AND il segnale PCwriteCond e Zero dalla
ALU, a sua volta l'uscita di AND in un OR con ingresso PCwrite che è il segnale di controllo per i
salti incondizionati! La figura sottostante ci mostra tutto questo apparato.
Adesso vediamo tutti i segnali di controllo con questa tabella che divide quelli ad 1bit da quelli
a 2bit.

Azioni dei segnali di controllo di 1bit

Segnale Effetto quando affermato Effetto quando non affermato


Il numero del registro destinazione
Il numero del registro destinazione per
RegDst per Registro scritto proviene dal
Registro scritto proviene dal campo rd
campo rt
Nel registro specificato sull'ingresso
RegWrite Nessuno Registro scritto è scritto il valore
presente sull'ingresso Dato scritto
Il primo operando della ALU è il registro
ALUsrcA Il primo operando della ALU è PC
A
Il contenuto della cella di memoria
MemRead Nessuno determinata dall'ingresso Indirizzo è
posto sull'uscita MemData
Il conenuto della cella di memoria
determinata dall'ingresso Indirizzo è
MemWrite Nessuno
sostituito dal valore presente
sull'ingresso Dato scritto
Il valore inviato all'ingresso Dato
Il valore inviato all'ingresso Dato
MemtoReg scritto dei registri proviene da
scritto dei registri proviene da MDR
ALUout
L'indirizzo fornito alla memoria L'indirizzo fornito alla memoria proviene
IorD
proviene da PC da ALUout
IRwrite Nessuno L'uscita della memoria viene scritta in IR
PC viene scritto; la provenienza del
PCwrite Nessuno
valore è controllata da PCsource
PCwriteCon PC viene scritto se anche l'uscita Zero
Nessuno
d della ALU è attiva
Azioni dei segnali di controllo di 2bit

Segnale Valore Effetto


ALUop 00 La ALU calcola una somma
01 La ALU calcola una sottrazione
La ALU calcola l'operazione determinata dal
10
campo funct dell'istruzione
Il secondo ingresso della ALU proviene dal
ALUsrcB 00
registro B
01 Il secondo ingresso della ALU è la costante 4
Il secondo ingresso della ALU è il valore dei
10 16bit meno significativi di IR, estesi a 32bit
con segno
Il secondo ingresso della ALU è il valore dei
11 16bit meno significativi di IR, estesi a 32bit
con segno e scalati a sinistra di 2bit
PCsource 00 In PC viene scritto l'uscita della ALU (PC+4)
In PC viene scritto il contenuto di ALUout
01 (l'indirizzo di destinazione del salto
condizionato)
Il PC viene scritto l'indirizzo di destinazione
10 del salto indondizionato (IR[25-0] scalato a
sinistra di 2bit e concatenato con PC+4[31-28])
Suddivisione dell'esecuzione
Vediamo dunque come effettivamente l'unità di elaborazione gestisce le istruzioni, in
particolare come sono divise in passi. Iniziamo con il darci una regola precisa: ogni passo deve
avere un'operazione con la ALU oppure un accesso a registro oppure un accesso a memoria!
Inoltre durante un ciclo di clock tutti i dati devono essere memorizzati per l'uso che ne deve
fare il passo successivo. Si ricordi inoltre che l'accesso al register-file richiede un ciclo di clock
aggiuntivo in quanto il tempo d'accesso ai registri individuali è nettamente inferiore! In questo
modo il ciclo di clock risulta meno lungo e non ci sono perdite di tempo...

Ogni istruzione richiede da 3 a 5 passi. Qui di seguito sono elencati i possibili passi di una
istruzione.

1. Prelievo dell'istruzione

Si usa PC come indirizzo di memoria, si esegue una lettura di istruzione e la si scrive in IR, dove
starà fino alla prossima istruzione. Si incrementa PC di 4.

IR = Memoria[PC];
PC = PC+4;

Per far tutto ciò dobbiamo affermare i segnali MemRead e IRwrite, porre IorD a 0 (zero) per
selezionare PC come sorgente dell'indirizzo. L'incremento di PC necessita il segnale non
affermato di ALUsrcA (invio di PC alla ALU), un segnale 01 di ALUsrcB (invio di 4 alla ALU) e un
ALUop a 00 (effettua la somma). Poi memorizziamo il nuovo indirizzo con PCwrite affermato su
PC. Queste due azioni avvengono in parallelo!

2. Decodifica dell'istruzione e caricamento dei registri

Non avendo ancora interpretato l'istruzione possiamo solamente effettuare azioni generali e
che valgano in ogni caso. Eseguire operazioni anche se queste non sono previste dall'istruzione
in particolare risulta essere una sorta di anticipazione che ottimizza la procedura riducendo il
numero di cicli di clock.

Possiamo quindi registrare i valori rs e rt nei registri A e B e memorizzare il valore dell'indirizzo


di salto condizionato in ALUout.

A = Reg[IR[25-21]];
B = Reg[IR[20-16]];
ALUout = PC + (sign-extend(IR[15-9]) << 2);

Accediamo al register-file e memorizziamo i dati di rs e rt in A e B, già che ci siamo calcoliamo


l'indirizzo di destinazione del salto portando a 0 (zero) ALUsrcA (invia PC alla ALU), ad 11
ALUsrcB (invia il campo offset, scalato ed esteso, alla ALU) e a 00 ALUop (effettua la somma).
Queste due azioni avvengono in parallelo!
3. Calcolo dell'indirizzo di memoria o completamento del salto

Questo è il primo passo determinato dall'istruzione. Le possibili esecuzioni, date le istruzioni


analizzate fino ad adesso, sono:

• Accesso alla memoria

ALUout = A + sign-extend(IR[15-0]);

La ALU somma gli operandi in modo da ottenere l'indirizzo della memoria. Ciò necessita di 1 in
ALUsrcA (invia A al primo ingresso di ALU), 10 in ALUsrcB (invia al secondo ingresso ALU, l'uscita
dell'unità di estensione del segno) e 00 in ALUop (effettua la somma).

• Istruzione logico-aritimetica

ALUout = A op B;

La ALU esegue l'operazione richiesta dall'istruzione tipo-R. ALUsrcA = 1, ALUsrcB = 00


(combinati inviano A e B alla ALU) e ALUop = 10 (i segnali di controllo della ALU sono generato
dal funct dell'istruzione)

• Salto condizionato (branch)

if (A==B) then PC = ALUout;

La ALU permette di sapere, tramite una sottrazione se i due registri contengono valori uguali o
diversi. In questo caso si pone ALIsrcA = 1 e ALUsrcB = 00 (A e B alla ALU), ALUop = 01 (effettua
sottrazione). Il segnale PCwriteCond = 1 per autorizzare la scrittura su PC nel caso Zero sia
affermato. PCsource = 01 per registrare in PC l'indirizzo del salto condizionato.

• Salto incondizionato (jump)

PC = PC[31-28] || (IR[25-0] << 2);

Il valore di PC è sostituito dall'indirizzo del salto. PCsource = 10, PCwrite = 1.

4. Accesso alla memoria o completamento dell'istruzione tipo-R

In questo passo le istruzioni load/store accedono alla memoria, mentre quelle tipo-R scrivono il
risultato!

• Istruzione load/store

● In caso di load:

MDR = Memoria [ALUout];

MemRead = 1, IorD = 1 (utilizzo dell'indirizzo di memoria proveniente dalla ALU e non dal PC)

● In caso di store:
Memoria [ALUout] = B;

MemWrite = 1

• Istruzione tipo-R

Reg[IR[15-11]] = ALUout;

Si deve porre ALUout in rd! Quindi RegDst = 1 (si seleziona di scrivere in rd), RegWrite = 1,
MemtReg = 0 (per
scrivere i dati uscenti dalla ALU al posto di quelli uscenti dalla memoria)

5. Completamento della lettura da memoria

Viene conclusa la procedura load scrivendo il valore MDR nel register-file.

Reg[IR[20-16]] = MDR;

MemtReg = 1 (per scrivere il dato proveniente dalla memoria), RegWrite = 1 (provoca la


scrittura) e RegDst = 0 (scelto il campo rt come registro).

Riassumiamo tutto in una tabella.

Azione per Azione per Azione per


Passo Azione per jump
tipo-R load/store branch

IR = Memoria[PC]
Prelievo
dell'istruzione
PC = PC+4
A = Reg[IR[25-21]]
Decodifica
dell'istruzione
B = Reg[IR[20-16]]
/ caricamento
dei registro
ALUout = PC + (sign-extend(IR[15-0]) << 2)

Calcolo
ALUout = A + PC = PC[31-28]
dell'indirizzo if (A==B) then
ALUout = A op B sign-extend(IR || (IR[25-0]
/ completamento PC = ALUout
[15-0]) <<2)
dei salti

Accesso alla Load: MDR =


memoria / Memoria[ALUout]
Reg[IR[15-11]
completamento
=ALUout
dell'istruzione Store: Memoria
tipo-R [ALUout[ = B

Completamento
Load: Reg[IR
della lettura
[20-16]] = MDR
da memoria
Unità di controllo per il multi-ciclo
L'unità di controllo della CPU a singolo ciclo è stata facile da progettare in quanto richiedeva
solamente una tabella delle verità! In quella a multi-ciclo, invece, le istruzioni sono eseguite in
più passi. L'unità di controllo deve quindi specificare i segnali da affermare in ogni passo e il
passo successivo della sequenza! Vedremo adesso un metodo per la progettazione della nuova
unità di controllo.

Quello che faremo adesso è progettare l'unità con una macchina a stati finiti. Con la macchina
a stati finiti, le istruzioni sono definite tramite una funzione di stato futuro che mette in
relazione lo stato presente e gli ingressi con uno stato futuro! Ogni stato definisce anche
l'affermazione o la non affermazione delle uscite stabilendo inoltre che i segnali non
espressamente affermati vanno interpretati come non affermati e non come indifferenti! I
segnali di controllo dei multiplexer sono invece esterni a questa logica in quanto non possono
avere valori indifferenti. La nostra macchina a stati finiti avrà i cinque passi analizzati
precedentemente. In particolare avremo varie parti in quanto i primi due passi sono uguali per
ogni tipo di istruzione, i passi da 3 a 5 sono invece a seconda dell'istruzione! Al termine
dell'esecuzione la nostra macchina dovrà ritornare allo stato iniziale.

La figura sottostante è la solita versione grafica dei primi due passi. Lo stato 0 sarebbe il passo
1 e via dicendo. All'interno dei cerchi che rappresentano gli stati ci sono i segnali affermati.
Come possiamo vedere dopo lo stato 1, lo stato futuro dipende dall'istruzione che come
abbiamo visto può generare un massimo di quattro passi.

La seconda figura è l'accesso a memoria. E' composta da quattro stati. Il primo è il calcolo della
memoria, dopo aver prelevato l'istruzione e il registro. Il secondo è la lettura o la scrittura in
memoria. Ne caso l'istruzione fosse lw, lo stato futuro sarà il 3 altrimenti il 5. Dopo ciò se
l'istruzione è lw è necessario un'altro stato per scrivere il dato dalla memoria nel register-file.
Se l'istruzione fosse stata sw, non avendo altro da fare lo stato futuro passerebbe allo stato 0.
L'implementazione dell'istruzione tipo-R richiede due stati. Lo stato 6 effettua il calcolo e lo
stato 7 registra il dato nel register-file.
Nei salti condizionati basta uno stato in cui avviene il confronto tra gli operandi e la scrittura
condizionata del PC.

Il salto incondizionato richiede uno stato solo che permette la scrittura del PC dato il nuovo
indirizzo.
Ecco lo schema finale.
Eccezioni
Già di per se l'unità di controllo è la parte più difficile da realizzare... ma uno degli aspetti più
spinosi è il controllo delle eccezioni. Le eccezioni sono causate quando un evento inatteso
informa la CPU! Questo evento può essere un errore nel calcolo oppure la richiesta di un device
di interagire con la CPU. Ciò significa che ogni tipo di eccezione deve essere presa in
considerazione e inserita in un contesto dinamico! Per ciò che dobbiamo fare noi, la nostra CPU
si può aspettare solo due eccezioni: l'overflow quando il segnale di overflow dell'ALU è
affermato e istruzione non valida quando non c'è nessun stato futuro per il codice op dato dalla
memoria. C'è una differenza da tener conto tra eccezione e interrupt... a seconda del
contesto in cui ci troviamo questi due nomi assumono un significato ben preciso. Nel caso del
MIPS eccezione sono i casi inattesi interni, l'interrupt quelli esterni (come la chiamata di di
device), per l'architettura 80x86 vale solo interrupt per tutti gli eventi, per PowerPC l'eccezione
è l'evento inconsueto e interrupt la variazione del normale flusso di controllo!

Quando si genera un'eccezione abbiamo bisogno di un registro dove memorizzare l'indirizzo di


memoria che ha creato l'evento anomalo e un registro dove memorizzare la causa di questo
evento. Il primo è l'EPC (Exception Program Counter), il secondo è Causa. Assumiamo che
Causa a 32bit abbia il bit meno significativo indicante il motivo: 0 per istruzione indefinita e 1
per overflow! Abbiamo bisogno di conseguenza di due segnali di controllo per la scrittura dei
registri: EPCwrite e CasusaWrite. Di un segnale a 1bit per fornire il corretto bit al bit meno
significativo di Causa: CausaInt. Infine dovendo scrivere PC in EPC abbiamo bisogno che EPC sia
collegato alla ALU. Per quale motivo? Quando viene avviata una nuova istruzione PC diventa
PC+4! Se noi memorizzassimo PC direttamente in EPC, in EPC avremo in verità PC+4! Abbiamo
bisogno quindi di effettuare PC-4 prima di memorizzarlo!

Le due eccezioni nella macchina a stati finiti saranno collegati all'istruzione per il calcolo (stato
7) e a quella della decodifica dell'istruzione (stato 1). Ecco lo schema finale sia del circuito sia
della macchina a stati finiti.