Abbiamo completato la nostra unità di elaborazione dati elementare, manca l'unità di controllo. Questa unità dovrà accettare dei valori di ingresso (una istruzione da eseguire) e generare:
Il controllo della ALU è particolare e conviene progettarlo prima di altre parti dell'unità di controllo.
Progetteremo una ALU in grado di eseguire le operazioni che il datapath che abbiamo sviluppato è in grado di fare ovvero:
ld
sd
beq
add
, sub
, and
, or
Quello che dobbiamo fare adesso è programmare le scelte che devono essere prese, ovvero l'unità di controllo deve distinguere il tipo di istruzioni che arrivano, in modo da ignorare o attivare determinati circuiti combinatori.
(Se necessario rivedi: ALU a 1 bit)
L'ALU che abbiamo progettato aveva 4 linee di controllo per distinguere le operazioni da effettuare (nel nostro caso, per semplicità stiamo considerando solo le 4 evidenziate in figura):
Il punto è che le operazioni della ALU non vengono utilizzate solo da istruzioni che effettuano operazioni logico-aritmetiche sui registri, ma anche da altri tipi di istruzione, come per esempio il caricamento dalla memoria (ld
) (quando viene usata la add per sommare l'offset all'indirizzo base).
La nostra unità di controllo centrale quindi deve essere in grado di distinguere il tipo di istruzione per vedere cosa mandare al controllo della ALU.
L'unità di controllo centrale prende il codop codice operativo (specificato in ogni istruzione nel campo codop), distingue il tipo di operazione, viene mandato al controllo della ALU il tipo di operazione da "attivare", il controllo della ALU poi deve sapere cosa fare in base ai campi dell'istruzione func3 e func7 che specificano ancora di più (per la ALU) quale operazione esattamente "far funzionare".
Esempio: l'unità di controllo centrale vede che il codice operativo dell'istruzione letta dal PC è un branch, l'unità di controllo centrale (che chiameremo d'ora in poi UC) comunica all'unita di controllo della ALU che l'operazione è un branch, la ALU deve sapere che quando deve fare un'operazione di tipo branch deve eseguire una sottrazione.
La nostra unità di elaborazione può effettuare:
ld
sd
beq
add
, sub
, and
, or
Per comunicare il tipo di operazione quindi l'UC invierà 2 bit alla ALU.
Quali sequenza di bit assegnare a quale operazione è una decisione presa come standard. Il libro fa come segue:
Il PC passa il campo ALUop all'UC, che capisce di che tipo di funzione si tratta, se si tratta di funzioni del tipo sd/ld comunica alla ALU una sequenza di 4 bit (ingresso controllo alla ALU) per l'operazione che deve eseguire, stessa cosa avviene per beq. Quando invece l'UC invia una operazione di tipo R, allora l'unità di controllo della ALU vede che deve essere eseguita una istruzione del tipo R, per cui ha bisogno di guardare func3 e func7 per capire effettivamente quale operazione la ALU deve eseguire. Nel caso di ld/sd/beq, func3 e func7 hanno delle XXX, il motivo è che in quei casi l'UC passa alla ALU direttamente l'operazione da fare, quindi la ALU
Partiamo da ciò che abbiamo appena detto sopra questo paragrafo.
Prima di tutto costruiamo una tavola di verità in base ai ragionamenti fatti sopra.
Adesso faremo un esempio calcolando una equazione logica, estrapolandola da questa tabella di verità:
Dopo aver fatto tale calcolo per tutte le righe, il circuito che viene fuori, già ottimizzato è il seguente:
diversi valori si ripetono molto spesso e sono stati rimossi (per esempio gli zeri ne campo func3 e func7).
I campi rs1 e rs2 contengono il numero dei registri sorgenti e rd contiene il numero del registro di destinazione. L'operazione da eseguire è codificata nei campi func3 e func7.
rs1 è il registro base il cui contenuto viene sommato al campo immediato di 12 bit per ottenere l'indirizzo del dato in memoria. Il campo rd è il registro destinazione per il valore letto.
rs1 è il registro base il cui contenuto viene sommato al campo immediato di 12 bit (suddiviso in due posizioni separate nell'istruzione) per ottenere l'indirizzo del dato in memoria. Il campo rs2 è il registro sorgente il cui valore viene copiato nella memoria.
I registri rs1 e rs2 vengono confrontati. Il campo immediato di 12 bit viene preso, il suo bit di segno esteso, shiftato a sinistra di una posizione e sommato al PC per calcolare l'indirizzo di destinazione del salto.
Prende dall'istruzione puntata dal PC il codop, eventualmente (nel caso delle operazioni del tipo R) i campi func3 e func7 sono servono come campi di estensione del codice operativo.
Il primo registro si trova sempre nei bit in posizione 19-15:
tale campo specifica anche il registro base per le istruzioni di load e store.
Il secondo registro si trova sempre nei bit in posizione 24-20 per le istruzioni del tipo R, S e SB:
tale campo specifica anche il registro il cui contenuto viene copiato in memoria nelle istruzioni di store.
Il registro di destinazione si trova sempre in posizione 11-7 (rd):
Il secondo operando (rs2) può anche essere costituito dai 12 bit di offset per le istruzioni di branch o di load/store.
Nota: tuti gli elementi di stato hanno clock implicito per controllare le operazioni di scrittura.
L'unità di controllo imposta tutti i segnali di controllo ad eccezione di PCSrc, per farlo si basa esclusivamente sul codice operativo prelevato dall'istruzione letta.
PCSrc è "accesa" se l'istruzione da fare è un branch, ma anche se l'uscita Zero della ALU, utilizzata per il confronto di uguaglianza, è anch'essa impostata a 1. Quindi colleghiamo in AND un segnale uscente dall'UC, che verrà chiamato Branch, con l'uscita Zero della ALU.
L'UC ha in ingresso solo il codop.
Invece in uscita sputa:
Immaginiamo di dover eseguire l'istruzione: add x1, x2, x3
Per prima cosa il PC possiede l'indirizzo dell'istruzione in memoria.
Tale indirizzo localizza una istruzione.
L'istruzione ha un codop che viene passato all'unità di controllo di principale.
Nel frattempo l'indirizzo del PC attuale è passato al sommatore che per passare all'istruzione successiva aggiunge 4 byte. Essendo la linea di controllo di branch pari a 0, qualsiasi cosa in AND con tale entrata, fa 0, dunque viene scelta la porta del MUX a 0 quando il PC deve incrementare.
L'unità di controllo vede il codop vede che una istruzione del tipo R e passa all'unità di controllo della ALU il codice relativo al tipo di istruzione R.
L'unità di controllo della ALU guarda nei campi func3 e func7 e si rendere conto che l'operazione da fare è una somma, allora passa alla ALU il codice operativo per selezionare la somma.
Nel frattempo i registri rs1, rs2 e rd hanno raggiunto il register file, e vengono mandati in uscita gli operandi rs1 e rs2.
Allo stesso tempo l'UC elabora i bit per le linee di controllo.
ALUSrc avrà valore 0, perché non deve essere effettuato un accesso in memoria.
RegWrite avrà valore 1, perché il risultato della somma deve essere scritto in rd (x1).
MemWrite e MemRead e MemToReg sono a 0, perché non avviene né una lettura dalla memoria, né una scrittura in memoria.
L'uscita Zero della ALU ha un valore sporco, che è collegato in AND con il bit branch, che è impostato a 0, quindi il PC salta semplicemente di una istruzione.
La ALU calcola la somma e la manda sia alla memoria che al mux collegato a MemToReg che è a 0, il che fa tornare il risultato della somma in Dato da scrivere dentro il register file ed essendo RegWrite abilitato viene scritto il risultato in rd (x1).
Tutti i percorsi vengono effettuati lo stesso (avendo valori sporchi) l'UC fa sì che vengano effettuate le operazioni richieste. Per esempio rs2 è mandato anche in Dato da scrivere in memoria, ma essendo MemRead/Write disattivi, viene ignorato.
Immaginiamo di dover eseguire l'istruzione: ld x1, offset(x2)
Per prima cosa il PC possiede l'indirizzo dell'istruzione in memoria.
Tale indirizzo localizza una istruzione.
L'istruzione ha un codop che viene passato all'unità di controllo di principale.
Nel frattempo l'indirizzo del PC attuale è passato al sommatore che per passare all'istruzione successiva aggiunge 4 byte, essendo la linea di controllo di branch pari a 0, qualsiasi cosa in AND con tale entrata, fa 0, dunque viene scelta la porta del MUX a 0 quando il PC deve incrementare.
L'unità di controllo vede il codop vede che una istruzione del tipo load e passa all'unità di controllo della ALU il codice relativo all'istruzione che deve effettuare: una somma.
I registri passano nel register file: rs1 è x2 ed è l'indirizzo base della memoria.
RegWrite è 1, poiché si deve scrivere il valore prelevato dalla memoria in rd.
Questa volta ALUSrc è 1 poiché si deve sommare un offset al suo indirizzo base.
La somma viene mandata nella memoria.
MemRead sarà a 1. Mentre MemWrite sarà a 0.
Viene prelevato il dato dalla memoria.
MemToReg sarà a 1, poiché si deve scrivere nel registro il valore prelevato.
Tale valore torna indietro in Dato da scrivere nel register file, essendo RegWrite a 1, il dato viene scritto.
Immaginiamo di dover eseguire l'istruzione: beq x1, x2, offset
Per prima cosa il PC possiede l'indirizzo dell'istruzione in memoria.
Tale indirizzo localizza una istruzione.
L'istruzione ha un codop che viene passato all'unità di controllo di principale.
Nel frattempo l'indirizzo del PC attuale è passato al sommatore che per passare all'istruzione successiva che dovrebbe aggiungere 4 byte, essendo la linea di controllo, questa volta, di branch pari a 1, c'è la possibilità che venga fatto un salto condizionato, dipende tutto se la porta Zero della ALU sputa 1.
L'unità di controllo vede il codop vede che una istruzione del tipo beq e passa all'unità di controllo della ALU il codice relativo all'istruzione che deve effettuare: una sottrazione.
Nel frattempo in rs1 è passato x1.
In rs2 passa x2.
In rd passa un dato sporco (viene fatta l'estensione dei bit del campo immediato).
MemRead è a 0.
MemWrite è a 0.
MemToReg è a 0.
RegWrite è a 0.
ALUSrc è a 0, poiché si deve fare la sottrazione tra x1 e x2.
L'estensione dei bit immediati arriva fino al modulo di shift.
Viene effettuata la sottrazione tra x1 e x2, se fa 0, l'uscita Zero della ALU restituisce 1, che messo in AND con branch, anch'esso 1, viene selezionata l'uscita del MUX 1 e quindi viene fatta la somma tra il PC e l'estensione dei 12 bit shiftata di 1 a sinistra.
Se la sottrazione non fa 0, il PC avanza normalmente.