Esercizio 27pipeline MIPS, dipendenze e cicli totali di un ciclo
In questa pagina 6
Testo. 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).
Convenzione usata: si conta anche lo stadio WB di BENZ, perché ogni istruzione attraversa tutti e 5 gli stadi e l'esecuzione finisce quando l'ultima ha completato il WB: 1091 cicli, valore della soluzione allegata al testo. Se invece si considera che il WB di BENZ non fa nulla e non si conta, il totale è 1090.
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 (LW, ADDI $1, SW, ADDI $2, SUB, BENZ), pipeline a 5 stadi con forwarding. Il salto è trattato con stallo fino al calcolo dell'indirizzo target: il prelievo successivo parte 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 →, 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.
Iterazioni: $2 cresce di 4 a ogni giro e $3 vale all'inizio $2 + 396; il ciclo termina quando . Quindi iterazioni.
Dipendenze sui dati:
$1:LW→ADDI $1,$1,1(distanza 1): load-use, bypass daMEM/WB.LMDma 1 stallo.$1:ADDI $1→SW $1,0($2)(distanza 1): il dato dellaSWsi legge in ID, quindi laSWaspetta il WB diADDI: 2 stalli.$2:ADDI $2→SUB $4,$3,$2(distanza 1): bypassEX/MEM.ALUOutput, nessuno stallo.$4:SUB→BENZ(distanza 1): bypassEX/MEM.ALUOutput, nessuno stallo.
La SW usa come base il $2 dell'iterazione precedente, già nel banco registri.
Evoluzione di una iterazione:
| istruzione | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
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 |
- Cicli 3-4:
ADDI $1entra in EX al ciclo 5 (1 stallo). - Cicli 5-7:
SWaspetta il WB diADDI $1(ciclo 7): ID ai cicli 5, 6, 7 ed EX all'8 (2 stalli). - Ciclo 10:
SUBriceve$2dall'uscita ALU diADDI $2(in MEM). - Ciclo 11:
BENZvaluta la condizione con$4appena calcolato. - Ciclo 12:
BENZè in MEM, l'indirizzo target è noto e ilLWsuccessivo ha l'IF. Quindi un'iterazione dura 11 cicli.
Totale:
- Le prime 98 iterazioni durano ciascuna 11 cicli: .
- L'ultima iterazione va dal suo IF al WB di
BENZ(ciclo 13): cicli. Convenzione: si conta il WB diBENZ(come la soluzione del corso); senza contarlo, che non fa nulla, sono 1090.
Senza stalli (CPI ideale) servirebbero cicli.
Verifica: il simulatore nella nota dà 13 cicli per una iterazione, 24 per due, 1091 per 99, passo di 11 cicli tra le iterazioni.
Errori comuni:
- : le iterazioni si sovrappongono e il passo è 11.
- Contare 100 iterazioni, o 396, invece di 99.
- Load-use dimenticato:
LW→ADDIcosta uno stallo anche con forwarding. - Vincolo della
SWignorato: il dato si legge in ID, dopo il WB della produttrice. - Far partire l'IF successivo al salto prima che l'indirizzo target sia noto.