per poter implementare la pipeline all’interno di una CPU MIPS, per permettere il forwarding è necessario inserire dei registri tra le unità funzionali, per poterci inserire dati e utilizzarli quando necessari.
esempio:
qui il problema si genera perché la
sw
effettua l’instruction fetch prima che lalw
scriva su$t4
- quindi, nel banco registri, c’è il registro$t6
pronto ad essere scritto (e non$t4
)per questo, tutte le informazioni ed i segnali di controllo devono trovarsi nel registro precedente della pipeline (e non dove sarebbero normalmente)
logica dei salti
aggiungendo la logica dei beq
si spostano tutti i controlli (tranne RegWrite
, che però viene attivato solo durante il Write Back) dopo l’Instruction Decode così da non avere la necessità di effettuare controlli solo durante le ultime tre fasi dell’istruzione .
si noti che ora serve il campo
funz
(codice funzione) di 6 bit, che esce dal registro ID/EX e viene utilizzato come segnale di controllo della ALU.
segnali di controllo Control Unit
si possono quindi dividere i segnali di controllo in 3 gruppi:
- fase EXE
- fase MEM
- fase WB
e i segnali si propagano all’interno della CPU
CPU completa con segnali di controllo
scoprire un data hazard in EXE
immaginiamo questo codice:
- in questo esempio, visto che il risultato del
sub
viene scritto in$s2
solo nella fase di WB della prima istruzione, le istruzioniand
eor
leggeranno il valore sbagliato. - è facile però notare che il risultato della sottrazione sia già disponibile alla fine della fase EX
- anche
and
eor
hanno bisogno del dato all’inizio della fase EX, quindi è possibile evitare stalli propagando il dato alle unità che lo richiedono non appena sia disponibile
possiamo quindi notare che le casistiche che portano a un data hazard in EXE sono:
casistiche
(la casistica dell’esempio è del primo tipo - EX/MEM.RegistroRd = ID/EX.RegistroRs = $s2
)
ma, dato che non tutte le istruzioni scrivono il risultato nel register file, questa strategia non basta e potrebbero esserci casi in cui viene propagato un dato non necessario.
- una possibile soluzione sarebbe controllare se il segnale
RegWrite
è attivo nella sezioneEX/MEM
eMEM/WB
- in più, dobbiamo controllare che il registro di destinazione non sia
$s0
, perché l’architettura MIPS impedisce di scriverci. - si aggiunge anche
MemRead == 0
perché, nel caso di istruzioni di tipo i, alcuni dei bit della parte immediata potrebbero essere interpretati come un registro e potrebbero risultare uguali a i registri usati in operazioni precedenti
quindi, si ha un data hazard in EXE se:
quindi, il forwarding in EXE si implementa così:
cosa fa?
consiste nel sostituire il valore letto dal blocco dei registri con quello prodotto dall’istruzione precedente (in fase EXE) o quella prima ancora (in fase MEM)
- modifiche al datapath: inserire un MUX prima della ALU per selezionare tra i tre casi:
- non c’è forwarding - il valore per la ALU viene letto dal registro
ID/EXE
della pipeline - forwarding dall’istruzione precedente - il valore per la ALU viene letto dal registro
EX/MEM
della pipeline - forwarding da due istruzioni prima - il valore per la ALU viene letto dal registro
MEM/WB
della ALU
- non c’è forwarding - il valore per la ALU viene letto dal registro
questo vale sia per il primo che per il secondo argomento della ALU (infatti sono presenti 2 MUX)
i segnali di controllo sono i seguenti:
scoprire data hazard in MEM
si ha un data hazard in MEM quando vengono fatti in sequenza un lw
e uno sw
con lo stesso registro $rt
(creando quindi una sorta di swap di valori in memoria)
è possibile rilevarlo se:
CPU con forwarding MEM
stallo dell’istruzione
a volte l’istruzione deve attendere che sia pronto il dato prima di poter effettuare il forwarding.
poiché il risultato della lw
non è disponibile prima della fase MEM, bisognerà aggiungere uno stallo.
per fermare l’istruzione con uno stallo dobbiamo (nella fase ID):
- annullare l’istruzione che deve attendere (bolla)
- azzerare i segnali di controllo
MemWrite
eRegWrite
eIF/ID.Istruzione
- rendendo l’istruzione una NOP
- azzerare i segnali di controllo
- rileggere la stessa istruzione affinché possa essere eseguita un ciclo di clock dopo:
- impedire che il PC si aggiorni
stallo in azione
quindi, la CPU in questo momento si presenterà così
CPU quasi completa
anticipare il jump
la decisione di eseguire il Jump viene presa dalla Control Unit nella fase ID - nel frattempo, un’altra istruzione è stata caricata ed occorre che si annulli.
ma è possibile anticipare il jump alla fase IF. per farlo, occorre:
- anticipare il riconoscimento dell’istruzione (che di solito avviene nella fase ID) con un comparatore con il valore dell’Opcode della j (000010)
- spostare la logica di aggiornamento del PC alla fase IF
così, la jump anticipata non introduce stalli
control hazard
l’istruzione beq
usa la ALU per fare il confronto tra i registri, per cui:
- il salto avviene dopo la fase EXE (nella fase MEM) ⇒ in caso di salto, le istruzioni seguenti già caricate vanno annullate
- necessita degli argomenti nella fase EXE ⇒ può aver bisogno di uno stallo se preceduta da una
lw
per anticipare la decisione di salto alla fase ID, occorre non usare la ALU
- inserendo un comparatore tra i due argomenti letti dal blocco registri
- spostando la logica di salto e il calcolo del salto relativo dalla fase EXE alla fase ID
- inserendo un’unità di forwarding apposita per la fase ID
CPU con branch anticipato
il flush identifica il branch, e “scarica” la pipeline di IF/ID (rendendo l’operazione successiva una nop)
ma l’abbassamento del numero di stalli (da 2 a 1) in caso di predizione sbagliata non è gratuito: infatti, la fase in cui bneq
e beq
necessitano dei dati viene anticipata da EXE ad ID
??? chiedi ???
cpu “finale” con pipeline
salto ritardato
la tecnica del salto ritardato consiste nell’inserimento di una o più istruzioni che verrebbero eseguite in entrambi i casi (salto o non salto) per evitare di dover inserire salti dopo un branch.