Esercizio 27pipeline MIPS, dipendenze e cicli totali di un ciclo
In questa pagina 6
Testo (esercizio di pipeline MIPS delle esercitazioni di Architettura degli Elaboratori, UniPD, a.a. 2010-11, con soluzione allegata). Considerando la pipeline MIPS vista a lezione, si consideri il frammento di codice:
loop: LW $1, 0($2) # R1 <- mem[0 + R2]
ADDI $1, $1, 1 # R1 <- R1 + 1
SW $1, 0($2) # mem[0 + R2] <- R1
ADDI $2, $2, 4 # R2 <- R2 + 4
SUB $4, $3, $2 # R4 <- R3 - R2
BENZ $4, loop # se R4 != 0 allora PC <- indirizzo(loop)supponendo che il valore iniziale di $3 sia $2 + 396.
a) Individuare e discutere le dipendenze dovute ai dati.
b) Mostrare come evolve la pipeline durante l'esecuzione del codice per le prime 6 istruzioni eseguite, assumendo la possibilità di forwarding (come visto a lezione per la pipeline MIPS) e che il salto condizionale (BENZ) sia trattato con stallo della pipeline fino al calcolo dell'indirizzo target. Calcolare inoltre il numero totale di cicli di clock necessari per portare a termine l'esecuzione completa del codice.
Teoria: PipelineIdea della catena di montaggio; pipeline a 5 stadi IF, ID, EX, MEM, WB; tempo di ciclo, tempo per n istruzioni in una pipeline a k stadi e speedup con esempi svolti; registri di pipeline; scrittura e lettura dei registri nello stesso ciclo; limiti (stadi sbilanciati, hazard).Pipeline →, Hazard nella pipelineHazard strutturali, sui dati e sul controllo; dipendenze RAW, WAR e WAW; stalli e bolle con diagrammi; data forwarding (bypass) e caso load-use; riordino delle istruzioni da parte del compilatore e dell'hardware; costo dei salti.Hazard nella pipeline →, Branch predictionTecniche per gli hazard sul controllo: stallo, flussi multipli, prelievo anticipato del bersaglio, loop buffer, salto ritardato; predizione statica e dinamica con bit di storia, predittore a 1 e a 2 bit con esempio su un ciclo, tabella dei bersagli (BTB); costo di una predizione errata.Branch prediction →. Esercizi analoghi: Esercizio 25 · pipeline MIPS con forwarding, stalli e bypass.
Quante volte gira il ciclo
$2 cresce di 4 a ogni iterazione e $3 vale all'inizio $2 + 396. BENZ ripete finché la differenza $4 = $3 - $2 è diversa da zero, cioè finché $2 non ha raggiunto $3: iterazioni.
a) Dipendenze sui dati
| dipendenza | produttrice → consumatrice | come si risolve |
|---|---|---|
$1 |
LW → ADDI $1,$1,1 (distanza 1) |
load-use: il dato è pronto a fine MEM di LW → bypass MEM/WB.LMD verso l'EX di ADDI, ma con 1 stallo |
$1 |
ADDI $1 → SW $1,0($2) (distanza 1) |
SW deve scrivere $1 in memoria: nel modello del corso non c'è bypass verso lo stadio MEM, quindi la SW legge $1 in ID e deve aspettare il WB di ADDI (stesso ciclo): stalli |
$2 |
ADDI $2 → SUB $4,$3,$2 (distanza 1) |
bypass EX/MEM.ALUOutput → ALU di SUB, nessuno stallo |
$4 |
SUB → BENZ (distanza 1) |
bypass EX/MEM.ALUOutput → l'unità che valuta la condizione in EX; nessuno stallo |
$2 |
ADDI $2 → LW/SW dell'iterazione dopo |
molto lontane: già scritto nel banco registri |
(La SW legge anche $2 come base, scritto dalla ADDI $2 precedente, ma quella ADDI è dopo la SW nel programma: nell'iterazione corrente la SW usa il $2 dell'iterazione precedente, già nel banco registri.)
b) Evoluzione delle prime istruzioni
Il salto è risolto alla fine di EX e la pipeline ferma il prelievo finché non si conosce l'indirizzo: l'istruzione successiva (il LW della iterazione dopo) viene prelevata nel ciclo in cui BENZ è in MEM.
| istruzione | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
LW $1, 0($2) |
IF | ID | EX | MEM | WB | |||||||||||||
ADDI $1, $1, 1 |
IF | ID | ID | EX | MEM | WB | ||||||||||||
SW $1, 0($2) |
IF | IF | ID | ID | ID | EX | MEM | WB | ||||||||||
ADDI $2, $2, 4 |
IF | IF | IF | ID | EX | MEM | WB | |||||||||||
SUB $4, $3, $2 |
IF | ID | EX | MEM | WB | |||||||||||||
BENZ $4, loop |
IF | ID | EX | MEM | WB | |||||||||||||
LW $1, 0($2) (iter. 2) |
IF | ID | EX | MEM | WB | |||||||||||||
ADDI $1, $1, 1 (iter. 2) |
IF | ID | ID | EX | MEM | WB |
Commento.
- Cicli 3-4.
ADDI $1,$1,1entra in EX al ciclo 5, non 4:LWproduce$1solo alla fine del suo MEM (ciclo 4), 1 stallo. - Cicli 5-7.
SW $1,0($2)aspetta il WB diADDI $1(ciclo 7): ID ai cicli 5, 6, 7 e EX al ciclo 8 (2 stalli; laADDI $2dietro è ferma in IF). - Ciclo 10.
SUBè in EX e riceve$2dall'uscita ALU diADDI $2(ora in MEM) tramiteEX/MEM: nessuno stallo. - Ciclo 11.
BENZvaluta la condizione in EX con$4appena calcolato daSUB(ora in MEM). - Ciclo 12.
BENZè in MEM: l'indirizzo target è noto e al ciclo 12 viene prelevato ilLWdella iterazione successiva. Il salto fa sovrapporre il MEM diBENZcon l'IF delLWseguente: un'iterazione dura 11 cicli (dal ciclo 1 al ciclo 12 il prossimo IF).
Numero totale di cicli
- Le prime 98 iterazioni hanno ciascuna 11 cicli "di passo": il
LWdi ogni iterazione inizia 11 cicli dopo quello precedente: . - L'ultima iterazione (la 99ª) non ha un successore: dal suo IF al WB di
BENZpassano 13 cicli (come nel diagramma: IF diLWal ciclo 1, WB diBENZal ciclo 13).
(Se si osserva che lo stadio WB di BENZ non fa nulla, l'ultima istruzione utile termina un ciclo prima: 1090 cicli.)
Confronto: senza stalli (CPI ideale = 1) servirebbero cicli. Il codice costa quasi il doppio per gli stalli del load-use, della SW e del salto.
Verifica con un simulatore
import re
def parse(riga):
riga = riga.strip()
op, resto = riga.split(None, 1)
a = [x.strip() for x in resto.split(",")]
if op == "LW":
return dict(op=op, dst=a[0], ex=[re.search(r"\((\$\d+)\)", a[1]).group(1)], mem=[], tipo="load")
if op == "SW":
return dict(op=op, dst=None, ex=[re.search(r"\((\$\d+)\)", a[1]).group(1)], mem=[a[0]], tipo="store")
if op == "BENZ":
return dict(op=op, dst=None, ex=[a[0]], mem=[], tipo="salto")
return dict(op=op, dst=a[0], ex=[x for x in a[1:] if x.startswith("$")], mem=[], tipo="alu")
def cicli_totali(corpo, iterazioni):
ins = [parse(r) for r in corpo] * iterazioni
ex, idc, ifc = [], [], []
for i, c in enumerate(ins):
if i == 0:
f, d = 1, 2
else:
f = ex[i - 1] + 1 if ins[i - 1]["tipo"] == "salto" else idc[i - 1] # salto: si preleva con il salto in MEM
d = max(f + 1, ex[i - 1]) # in ID quando la precedente è in EX
e = d + 1
for r in c["ex"] + c["mem"]:
for p in range(i - 1, -1, -1):
if ins[p]["dst"] == r:
if r in c["mem"]:
e = max(e, ex[p] + 3) # dato della store: dopo il WB
else:
e = max(e, ex[p] + (2 if ins[p]["tipo"] == "load" else 1)) # forwarding verso EX
break
ifc.append(f); idc.append(d); ex.append(e)
return ex[-1] + 2, ifc
corpo = ["LW $1, 0($2)", "ADDI $1, $1, 1", "SW $1, 0($2)", "ADDI $2, $2, 4", "SUB $4, $3, $2", "BENZ $4, loop"]
print(cicli_totali(corpo, 1)[0], cicli_totali(corpo, 2)[0]) # 13 24: 13 per una iterazione, +11 per ogni altra
tot, ifc = cicli_totali(corpo, 99)
print(tot) # 1091
print(ifc[6] - ifc[0]) # 11: passo tra una iterazione e la successiva
print(99 * 6 + 4) # 598: tempo senza stalliIl simulatore riproduce i numeri della soluzione allegata al testo (11 cicli per iterazione, 1091 in totale).
Errori comuni
- Moltiplicare (1287): le iterazioni si sovrappongono di 2 cicli (il MEM e il WB di
BENZcon l'IF e l'ID delLWsuccessivo) e il passo è 11. - Non contare le 99 iterazioni (396/4) ma 100 o 396.
- Dimenticare che
LW → ADDIcosta uno stallo anche con forwarding (load-use). - Dimenticare il vincolo della
SW(dato da scrivere letto in ID, nel ciclo del WB della produttrice). - Far partire l'IF dell'istruzione successiva a un salto prima che l'indirizzo target sia noto (qui: quando
BENZè in MEM).
Versione ripasso
Esercizio di pipeline MIPS (a.a. 2010-11): ciclo di 6 istruzioni, forwarding, salto trattato con stallo fino al calcolo dell'indirizzo target (prelievo successivo quando BENZ è in MEM). Teoria: PipelineIdea della catena di montaggio; pipeline a 5 stadi IF, ID, EX, MEM, WB; tempo di ciclo, tempo per n istruzioni in una pipeline a k stadi e speedup con esempi svolti; registri di pipeline; scrittura e lettura dei registri nello stesso ciclo; limiti (stadi sbilanciati, hazard).Pipeline →, Hazard nella pipelineHazard strutturali, sui dati e sul controllo; dipendenze RAW, WAR e WAW; stalli e bolle con diagrammi; data forwarding (bypass) e caso load-use; riordino delle istruzioni da parte del compilatore e dell'hardware; costo dei salti.Hazard nella pipeline →.
Iterazioni: $3 parte da $2 + 396 e $2 cresce di 4: .
Dipendenze: LW→ADDI $1 (load-use, 1 stallo); ADDI $1→SW (il dato della SW si legge in ID dopo il WB di ADDI: 2 stalli); ADDI $2→SUB e SUB→BENZ (bypass EX/MEM.ALUOutput, nessuno stallo).
Tempi (una iterazione): LW IF 1; ADDI $1 EX 5; SW EX 8; ADDI $2 EX 9; SUB EX 10; BENZ EX 11, MEM 12 → il LW successivo ha l'IF al ciclo 12: 11 cicli per iterazione; l'ultima ha WB di BENZ al ciclo 13.
Totale: cicli (1090 se si trascura il WB di BENZ); senza stalli .
Errori comuni: (iterazioni non sovrapposte); 100 iterazioni; load-use dimenticato; vincolo della SW ignorato; IF successivo prima della fine del calcolo dell'indirizzo.