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 (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: 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.}

(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 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, 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: 396/4=99396/4 = 99.

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: 98⋅11+13=109198 \cdot 11 + 13 = 1091 cicli (1090 se si trascura il WB di BENZ); senza stalli 99⋅6+4=59899 \cdot 6 + 4 = 598.

Errori comuni: 99×1399 \times 13 (iterazioni non sovrapposte); 100 iterazioni; load-use dimenticato; vincolo della SW ignorato; IF successivo prima della fine del calcolo dell'indirizzo.

Teoria collegata