Salta al contenuto
Note per Studenti Esercizio 27 · pipeline MIPS, dipendenze e cicli totali di un ciclo

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: 396/4=99396 / 4 = 99 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,1 entra in EX al ciclo 5, non 4: LW produce $1 solo alla fine del suo MEM (ciclo 4), 1 stallo.
  • Cicli 5-7. SW $1,0($2) aspetta il WB di ADDI $1 (ciclo 7): ID ai cicli 5, 6, 7 e EX al ciclo 8 (2 stalli; la ADDI $2 dietro è ferma in IF).
  • Ciclo 10. SUB è in EX e riceve $2 dall'uscita ALU di ADDI $2 (ora in MEM) tramite EX/MEM: nessuno stallo.
  • Ciclo 11. BENZ valuta la condizione in EX con $4 appena calcolato da SUB (ora in MEM).
  • Ciclo 12. BENZ è in MEM: l'indirizzo target è noto e al ciclo 12 viene prelevato il LW della iterazione successiva. Il salto fa sovrapporre il MEM di BENZ con l'IF del LW seguente: 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 LW di ogni iterazione inizia 11 cicli dopo quello precedente: 98×11=107898 \times 11 = 1078.
  • L'ultima iterazione (la 99ª) non ha un successore: dal suo IF al WB di BENZ passano 13 cicli (come nel diagramma: IF di LW al ciclo 1, WB di BENZ al ciclo 13).

98⋅11+13=1091 cicli di clock.98 \cdot 11 + 13 = 1091 \text{ cicli di clock.}

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 99×6+4=59899 \times 6 + 4 = 598 cicli. Il codice costa quasi il doppio per gli stalli del load-use, della SW e del salto.

Verifica con un simulatore

python
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 stalli

Il simulatore riproduce i numeri della soluzione allegata al testo (11 cicli per iterazione, 1091 in totale).

Errori comuni

  • Moltiplicare 99×1399 \times 13 (1287): le iterazioni si sovrappongono di 2 cicli (il MEM e il WB di BENZ con l'IF e l'ID del LW successivo) e il passo è 11.
  • Non contare le 99 iterazioni (396/4) ma 100 o 396.
  • Dimenticare che LW → ADDI costa 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 R4=R3−R2=0R4 = R3 - R2 = 0. Quindi 396/4=99396 / 4 = 99 iterazioni.

Dipendenze sui dati:

  • $1: LW → ADDI $1,$1,1 (distanza 1): load-use, bypass da MEM/WB.LMD ma 1 stallo.
  • $1: ADDI $1 → SW $1,0($2) (distanza 1): il dato della SW si legge in ID, quindi la SW aspetta il WB di ADDI: 2 stalli.
  • $2: ADDI $2 → SUB $4,$3,$2 (distanza 1): bypass EX/MEM.ALUOutput, nessuno stallo.
  • $4: SUB → BENZ (distanza 1): bypass EX/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 $1 entra in EX al ciclo 5 (1 stallo).
  • Cicli 5-7: SW aspetta il WB di ADDI $1 (ciclo 7): ID ai cicli 5, 6, 7 ed EX all'8 (2 stalli).
  • Ciclo 10: SUB riceve $2 dall'uscita ALU di ADDI $2 (in MEM).
  • Ciclo 11: BENZ valuta la condizione con $4 appena calcolato.
  • Ciclo 12: BENZ è in MEM, l'indirizzo target è noto e il LW successivo ha l'IF. Quindi un'iterazione dura 11 cicli.

Totale:

  • Le prime 98 iterazioni durano ciascuna 11 cicli: 98⋅11=107898 \cdot 11 = 1078.
  • L'ultima iterazione va dal suo IF al WB di BENZ (ciclo 13): 1078+13=10911078 + 13 = 1091 cicli. Convenzione: si conta il WB di BENZ (come la soluzione del corso); senza contarlo, che non fa nulla, sono 1090.

Senza stalli (CPI ideale) servirebbero 99⋅6+4=59899 \cdot 6 + 4 = 598 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:

  • 99×13=128799 \times 13 = 1287: le iterazioni si sovrappongono e il passo è 11.
  • Contare 100 iterazioni, o 396, invece di 99.
  • Load-use dimenticato: LW → ADDI costa uno stallo anche con forwarding.
  • Vincolo della SW ignorato: 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.

Teoria collegata