Vous êtes sur la page 1sur 216

Autori:

Albano Teodoro, Luigi Biasi, Giuseppe Narracci

Titolo Originale:
Calcolatori Elettronici, 1a edizione 2012

Copyright 2012
Tutti i diritti sono riservati a norma di legge e a norma delle convenzioni
internazionali. Nessuna parte di questo libro pu essere riprodotta con
sistemi elettronici, meccanici o altri, senza lautorizzazione degli autori.

Nessun calcolatore 9000 ha mai commesso un errore o alterato uninformazione.


Noi siamo, senza possibili eccezioni di sorta, a prova di errore, e incapaci
di sbagliare. (CALCOLATORE HAL 9000)
2001: Odissea nello spazio

ii

iii

Sommario
CAPITOLO 1
LA VALUTAZIONE DELLE PRESTAZIONI DI UN PROCESSORE.............................................................................. 1
1.

Filosofia Risc E Cisc ................................................................................................................................ 2

2.

La Legge Di Amdahl ............................................................................................................................... 3

CAPITOLO 2
CARATTERISTICHE DELLE ISTRUZIONI DI UN CALCOLATORE ............................................................................. 7
1.

Ciclo Istruzione ...................................................................................................................................... 7

2.

Categorie Di Formati Istruzione............................................................................................................ 8

3.

Struttura Dell'istruzione ....................................................................................................................... 8

CAPITOLO 3
CLASSIFICAZIONE DEI PROCESSORI ................................................................................................................... 9
1.

Classificazione per istruzione ............................................................................................................... 9

2.

Classificazione per parola ..................................................................................................................... 9

3.

Classificazione per la gestione della memoria interna ...................................................................... 10

4.

Classificazione per modi di indirizzamento ........................................................................................ 16

CAPITOLO 4
MODI DI INDIRIZZAMENTO ............................................................................................................................. 17
1.

Indirizzamento Diretto........................................................................................................................ 17

2.

Indirizzamento con Immediato .......................................................................................................... 17

3.

Indirizzamento con Scostamento (displaycement) ........................................................................... 18

4.

Indirizzamento con registro auto-incrementato o auto-decrementato ........................................... 19

5.

Indirizzamento per le operazioni di salto........................................................................................... 22

6.

Operazioni Nellinsieme Di Istruzioni ................................................................................................. 26

CAPITOLO 5
ISTRUZIONI DEL PROCESSORE MIPS ................................................................................................................ 29
1.

Istruzioni di tipo J ................................................................................................................................ 29

2.

Istruzioni di tipo R ............................................................................................................................... 30

3.

Istruzioni di tipo I ................................................................................................................................ 30

4.

Le istruzioni del calcolatore MIPS ...................................................................................................... 31


4.1.

Istruzioni di trasferimento dati .................................................................................................. 31

4.2.

Istruzioni per operazioni logico-aritmetiche .............................................................................. 33

4.3.

Istruzioni di controllo .................................................................................................................. 36

Esercizi ......................................................................................................................................................... 39
iv

CAPITOLO 6
ARCHITETTURA CALCOLATORE PER LE OPERAZIONI CON DATI INTERI........................................................... 41
1.

Fasi Di Una Istruzione ......................................................................................................................... 41

2.

Architettura Calcolatore Per La Gestione Di Dati Interi .................................................................... 42

3.

2.1.

Istruzione Di Tipo R ..................................................................................................................... 45

2.2.

Istruzione di tipo I ....................................................................................................................... 45

Meccanismo Di Funzionamento Interno Dell'ALU: ............................................................................ 50

CAPITOLO 7
LA PIPELINE ...................................................................................................................................................... 53
1.

Introduzione alla Pipeline ................................................................................................................... 53

2.

Architettura Pipeline........................................................................................................................... 57
2.1.

3.

La Tipica Vita Di Unistruzione In Pipeline .............................................................................. 60

Microcodice o Microistruzioni ............................................................................................................ 61


3.1.

IF (ISTRUCTION FETCH) ............................................................................................................... 62

3.2.

ID (ISTRUCTION DECODE) ........................................................................................................... 63

3.3.

EX (EXECUTE) ............................................................................................................................... 64

3.4.

MEM ............................................................................................................................................ 65

3.5.

WB ............................................................................................................................................... 65

CAPITOLO 8
PRESTAZIONI PROCESSORE PIPELINE .............................................................................................................. 67
1.

Miglioramento prestazioni: super pipeline........................................................................................ 67

2.

Miglioramento prestazioni: parallelismo ........................................................................................... 68

CAPITOLO 9
CONFLITTI NELLA PIPELINE .............................................................................................................................. 71
1.

Conflitti di Dato ................................................................................................................................... 72

2.

Conflitti sulle Diramazioni .................................................................................................................. 79

Esercizi ......................................................................................................................................................... 87
CAPITOLO 10
IL COSTO DI UN CIRCUITO INTEGRATO ........................................................................................................... 95
1.

La progettazione ................................................................................................................................. 95

2.

La produzione ...................................................................................................................................... 96

CAPITOLO 11
IL PROCESSORE FLOATING POINT
1.

Operazioni Floating Point ................................................................................................................... 99

2.

1.1.

Addizione................................................................................................................................... 100

1.2.

Moltiplicazione.......................................................................................................................... 101

Architettura Processore Floating Point ............................................................................................ 101

CAPITOLO 12
GERARCHIA DI MEMORIA .............................................................................................................................. 109
1.

Panoramica sulle memorie ............................................................................................................... 109

2.

Principi generali ................................................................................................................................ 111

CAPITOLO 13
MEMORIA CACHE
1.

Cenni storici sulle memorie cache .................................................................................................... 115

2.

Strategie di allocazione dei blocchi .................................................................................................. 115


2.1.

Memoria Completamente Associativa ..................................................................................... 116

2.2.

Indirizzamento Diretto.............................................................................................................. 116

2.3.

Set Associativa .......................................................................................................................... 116

3.

Strategia di ricerca e identificazione del blocco .............................................................................. 116

4.

Sostituzione di un blocco .................................................................................................................. 117

5.

Scrittura e lettura di un blocco ......................................................................................................... 118

CAPITOLO 14
PRESTAZIONI DELLA CACHE ........................................................................................................................... 123
1.

Analisi delle prestazioni della cache ................................................................................................ 123

2.

Cache dal punto di vista tecnologico................................................................................................ 125

3.

2.1.

Memoria statica SRAM ............................................................................................................. 125

2.2.

Memoria dinamica DRAM ........................................................................................................ 125

Dimensionamento della memoria dal punto di vista del tempo .................................................... 125

CAPITOLO 15
STRUTTURA ELETTRONICA DELLA CACHE...................................................................................................... 127
1.

Analisi elettronica della cache .......................................................................................................... 128

2.

Struttura delle memorie ................................................................................................................... 132

Esercizi ....................................................................................................................................................... 133


CAPITOLO 16
DIVERSE ARCHITETTURE DI MEMORIE .......................................................................................................... 135
1.

Aumentare la banda aumentando i dati letti o scritti: banchi paralleli ......................................... 136

2.

Aumentare la banda diminuendo il tempo: banchi interlacciati .................................................... 137

CAPITOLO 17
vi

POLITICHE DI GESTIONE DELLE MEMORIE .................................................................................................... 141


1.

Interazione con i dispositivi di I/O ................................................................................................... 141

2.

Interazione con pi processori ......................................................................................................... 144

CAPITOLO 18
TECNICHE DI COMPILAZIONE EFFICIENTI ...................................................................................................... 147
1.

Srotolamento del loop ...................................................................................................................... 147

2.

Pipeline da programma .................................................................................................................... 149

CAPITOLO 19
PARALLELISMO A LIVELLO DI ISTRUZIONI ..................................................................................................... 153
1.

Processore VLIW ............................................................................................................................... 153

2.

Processore superscalare ................................................................................................................... 157

CAPITOLO 20
PROCESSORE VETTORIALE ............................................................................................................................. 159
1.

Architettura processore vettoriale cray-1........................................................................................ 160

2.

Modello di programmazione vettoriale ........................................................................................... 162

3.

Vantaggi del processore vettoriale .................................................................................................. 163

4.

Processori a registri vettoriali vs processori memory-memory ...................................................... 166

5.

Vettorializzazione del codice ............................................................................................................ 167

6.

Vector Stripmining ............................................................................................................................ 168

7.

Parallelismo delle istruzioni vettoriali ............................................................................................. 170

8.

Alcune tipiche operazioni con i vettori ............................................................................................ 173

9.

Riduzione a scalare dei vettori ......................................................................................................... 174

10.

Set istruzioni del processore VMIPS ............................................................................................. 175

CAPITOLO 21
TASSONOMIA DI FLYNN................................................................................................................................. 185
1.

Classificazione dei processore secondo Flynn ................................................................................. 185

2.

Concetti base..................................................................................................................................... 185

CAPITOLO 22
INFRASTRUTTURE DI COMUNICAZIONE ........................................................................................................ 187
1.

Bus ..................................................................................................................................................... 187

2.

Rete punto-punto ............................................................................................................................. 187

3.

Rete ad anello ................................................................................................................................... 188

4.

Rete a centro stella ........................................................................................................................... 189

5.

Reti ibride .......................................................................................................................................... 189


vii

6.

Ipercubo ............................................................................................................................................ 189


6.1.

Trasferimento dei dati allinterno dellipercubo ..................................................................... 190

CAPITOLO 23
PREDIZIONE DINAMICA ................................................................................................................................. 195
1.

Predittore a un bit............................................................................................................................. 197

2.

Predittore a due bit........................................................................................................................... 197

CAPITOLO 24
ALGORITMO DI TOMASULO .......................................................................................................................... 201
1.

Buffer di Riordino.............................................................................................................................. 201

2.

Fasi dellistruzione ............................................................................................................................ 203

3.

Esecuzione fuori ordine .................................................................................................................... 206

viii

ix

CAPITOLO 1

LA VALUTAZIONE DELLE PRESTAZIONI DI UN PROCESSORE


Le prestazioni di un processore sono state storicamente valutate da una serie di parametri che nel tempo
sono stati sostituiti da altri. Per esempio un modo stato il numero di colpi di clock per unit di tempo, cio
la frequenza di clock. Prendiamo come esempio un processore da 60 MHz: il clock nel processore, emette
un impulso ogni 60milionesimo di secondo. In un secondo ci sono quindi 60 milioni di colpi di clock. Magari
questo processore viene considerato migliore di un altro che ne emette 45 milioni al secondo. Questa
distinzione per non ha senso se non indichiamo cosa avviene in un colpo di clock.
Il colpo di clock come il colpo di tamburo che il capo vogatore dava sulle galee romane. Gli schiavi
remavano e a ogni colpo di tamburo dovevano dare un certo numero di vogate. Se la vogata legata alla
battuta, una vogata per battuta, e chiaro che con 50 battute al minuto, ci sono 50 vogate al minuto. Ma si
pu benissimo pensare di legare una vogata ogni 5 battute, ogni 5 battute bisogna dare quindi una vogata.
Per cui lasciando da parte il singolo colpo di clock, ci si pu concentrare sul CONTO ISTRUZIONI. Mi
interessa sapere, in 1 secondo quante istruzioni processa un processore? E anche con il contro istruzioni
non avr molto senso se non mettiamo dei paletti. Questo viene ricavato da un altro parametro che si
chiama CLOCK PER INSTRUCTIONS (CPI). Se io so i colpi di clock e il clock per instruction allora ho il conto
istruzioni. Ad esempio sapendo che un processore con una frequenza pari ad 1 GHz e che esegue un
istruzione per colpo di clock(CPI = 1), ottengo che in 1 secondo eseguir 1 miliardo di istruzioni. Un altro
processore da 1 GHz che invece ha bisogno di 1,3 clock per istruzione, cio ha bisogno di 1,3 colpi di clock
per eseguire una istruzione, allora in 1 secondo il processore eseguir 1 miliardo/1,3 istruzioni.
Sapere che un processore esegue N istruzioni/sec e un altro ne esegue M istruzioni/sec ci aiuta a definire
quale tra questi due processori migliore? Solitamente saremmo portati a dire che quello che ne esegue di
pi dovrebbe essere migliore. Ma in realt questo non ci aiuta molto o meglio ci sarebbe di aiuto solo
nellipotesi in cui entrambi i processori abbiamo lo stesso SET DI ISTRUZIONI (INSTRUCTION SET).
Ora vediamo con accuratezza di cosa si tratta. Quando un processore effettua una operazione, esegue e
traduce tale compito in una sequenza di istruzioni macchina. Ad esempio quando bisogna eseguire un
operazione come A*B*C e scrivere il risultato in memoria allallocazione D il processore attraverso una
serie di istruzioni del SET DI ISTRUZIONI realizza questo compito:
MULT3

A, B, C, D

(1)

Con questa istruzione del processore vengono presi i valori dagli indirizzi di A, di B e di C, vengono
moltiplicati e il risultato salvato nellindirizzo D. Il processore prende il dato allindirizzo di ognuno , fa il
prodotto tra questi e scrive il risultato in D, con una sola istruzione.
Un secondo processore diverso dal primo per eseguire la stessa operazione magari dovr utilizzare pi
istruzioni del suo SET DI ISTRUZIONI(diverso dallaltro processore), e quindi avr bisogno di fare:
-

A (istruzione per prelevare A dalla memoria)


B (istruzione per prelevare B dalla memoria)
C (istruzione per prelevare C dalla memoria)
D1 = A*B ( istruzione per fare il prodotto tra A e B e salvarlo in D1)
1

D=D1*C ( istruzione per fare il prodotto tra D1 e C e salvarlo in D)


D (istruzione per scrivere D in memoria)

Sapendo che entrambi i processori eseguono 1 istruzione/sec mi da un idea per confrontare i due
processori. Il primo processore che compie loperazione con una sola istruzione necessiter di 1 secondo a
differenza del secondo processore, che ha bisogno di 6 istruzioni e quindi di 6 secondi. E chiaro che in
queste ipotesi il primo processore migliore.

Lultimo processore analizzato non ha nel suo SET DI ISTRUZIONI ,istruzioni cos potenti come MULT3
dellaltro processore . Questo processore ha bisogno di 6 istruzioni per eseguire quelloperazione.
Allora il CONTO ISTRUZIONI pi significativo del colpo di clock che un parametro che descrive
veramente poco e ci indica solamente la frequenza del treno di impulsi treno di impulsi. Allora pu avere
pi senso sapere effettivamente quante sono le istruzioni che vengono eseguite in un istante di tempo, e
comparare in questo modo due macchine a patto che esse abbiano lo stesso SET DI ISTRUZIONI. Quello che
realmente interessa allutente il tempo che deve aspettare affinch un problema venga risolto. Ora il
problema nella valutazione comparativa dei processori che un problema risolto da un sistema di calcolo
in un unit di tempo , mi porta a dare una valutazione estremamente orientata a quel problema (PROBLEM
ORIENTED) perch nessuno mi garantisce che cambiando il problema la macchina A resti pi veloce della
macchina B. Nella risoluzione di un algoritmo vengono messi in evidenza aspetti che sono differenti dalla
risoluzione di un altro algoritmo.
Quindi fare una valutazione comparativa tra due processori, e quindi sapere quali aspetti andare a
migliorare su un progetto, diventa un compito non banale.

1. Filosofia Risc E Cisc


Si presentano due filosofie di architettura di un processore per quanto riguarda il SET Di ISTRUZIONI: RISC e
CISC.
Un set di istruzioni RISC (Reduced Instruction Set Computing, calcolatore con insieme ridotto di istruzioni)
un tipo di architettura di microprocessore che si concentra sull'elaborazione rapida ed efficiente di una
serie relativamente piccola di istruzioni che comprende la maggior parte delle istruzioni che un calcolatore
codifica ed esegue. L'architettura RISC ottimizza le istruzioni di modo che possano venire seguite molto
rapidamente, in genere in un solo ciclo di clock. I chip RISC eseguono cos le istruzioni semplici pi
velocemente dei microprocessori CISC (Complex Instruction Set Computing) progettati per gestire una serie
pi vasta di istruzioni. Sono tuttavia pi lenti dei chip CISC quando si tratta di seguire istruzioni complesse
che, per poter essere eseguite da microprocessori RISC, devono essere suddivise in molte istruzioni
macchina.
Allora scegliere il tipo di filosofia assumer un senso quando si utilizzer un processore che prevede che
tutte le istruzioni siano molto simili tra di loro in modo da semplificare una parte non trascurabile di un
processore: lUNITA DI CONTROLLO. Progettare ununit di controllo che piloti ogni transistor del
processore per lesecuzione di ogni singola istruzione ha una certa complessit. Questa complessit pu
2

essere ridotta se le istruzioni sono molto simili tra di loro(RISC). Al contrario pilotare ogni transistor del
processore per eseguire tante diverse istruzioni(CISC), comporta un unit di controllo un po pi
complicata. Venuto meno il problema della memoria costruita con nuclei di ferrite, da tanti anni i
calcolatori CISC non esistono pi.

Tornando al discorso sulla valutazione delle prestazioni, quello che interessa vedere il tempo in cui un
problema viene svolto. Come abbiamo gi detto, se una macchina risolve un problema in un tempo minore
di un'altra macchina non vuole che con problemi diversi ha sempre caratteristiche di performance che la
portano a prevalere sullaltra macchina. Esistono dei programmi che hanno lo scopo di valutare le
prestazioni di un sistema. Questi programmi si chiamano BENCHMARK (banco di prova). Per mettere in
evidenza vari aspetti si crea un paniere di benchmark in cui c un programma che testa come quel sistema
gestisce gli accessi in memoria, un altro che testa come quel sistema fa i calcoli in virgola mobile, un altro
ancora che testa come quel sistema gestisce laccesso a grandi quantit di dati e cos via. Quindi in base a
questo paniere di benchmark, si riescono a valutare le prestazione di una macchina.
In fase di progettazione quando si ottengono delle prestazioni dal sistema e si individuano dei punti critici,
si pone il problema se ha senso o meno migliorare il sistema riguardo quel punto critico.
Tornando allesempio di (1), ammettiamo che il progettista del secondo processore (RISC) non abbia
problemi a inserire istruzioni di tipo CISC, ad esempio come MULT3. Questo ampliamento del set di
istruzioni avr un certo costo, bisogna fare in modo che lunit di controllo sappia gestire anche quella
istruzione, e supponiamo di aver quantificato questo costo, es. 2 transistor. Bisogna per tenere in
considerazione il beneficio che si trae rispetto al costo. Quindi scoprire che questa istruzione dopo averla
inserita mi consente di eseguire questo obiettivo in un secondo piuttosto che in 1,2 secondi quindi con un
aumento delle mie prestazioni del 20% pu essere utile a fronte del fatto che ho dovuto inserire 2
transistor su un unit di controllo che ne prevedeva 1000 con un costo dello 0,2 %. Questo per deve va in
relazione con il numero di volte in cui verr utilizzata nella vita di questo calcolatore listruzione , ad
esempio MULT3. Perch se questa istruzione viene utilizzata spesso allora effettivamente si
guadagnato, ma se quel calcolatore lavora con questa istruzione una volta su mille, questo 20% che mi ho
guadagnato non stato guadagnato in assoluto ma una volta su mille e quindi diventato 0,02%.

2. La Legge Di Amdahl
In questo discorso assume molta importanza la LEGGE DI AMDAHL.
Usando la legge di Amdahl si pu calcolare il guadagno in termini di prestazioni che si pu ottenere
migliorando una parte di un calcolatore. Questa legge afferma che il miglioramento di prestazioni che pu
essere ottenuto usando qualche modalit di esecuzione pi veloce limitato dalla percentuale di tempo in
cui pu essere utilizzata tale modalit. La legge di Amdahl definisce laumento di velocit (speedup) che si
pu ottenere usando una particolare caratteristica. Cos laumento di velocit? Immaginiamo di poter
apportare un miglioramento a un calcolatore in modo che le sue prestazioni migliorino. Laumento di
velocit il rapporto

Oppure equivalentemente
Speedup =( Tempo di esecuzione complessivo senza usare il miglioramento) /( Tempo di esecuzione
complessivo usando il miglioramento).

Il valore di Speedup ci dice quanto pi velocemente verr eseguito un programma usando il calcolatore
migliorato, rispetto al calcolatore originale. La legge di Amdhal fornisce un mezzo rapido per misurare lo
Speedup conseguente a un determinato miglioramento, che dipende da due fattori:

1. La frazione del tempo di esecuzione sul calcolatore originale che pu trarre vantaggio dal
miglioramento. Per esempio, se il miglioramento pu essere usato in 20 dei 60 secondi totali
richiesti per l'esecuzione di un programma, la frazione di cui parliamo 20 /60. Questo valore, che
chiameremo frazione migliorata, sempre inferiore al pi uguale a 1.
2. Il miglioramento di prestazioni che si ottiene nel modo di esecuzione migliorato, cio quanto
verrebbe seguito pi velocemente il programma se il modo di esecuzione migliorato potesse essere
utilizzato per l'intero programma. Questo valore uguale al tempo di esecuzione nella modalit
originaria diviso il tempo di esecuzione nella modalit migliorata. Se nella modalit migliorata
occorrono, per esempio, due secondi per eseguire una parte del programma che richiede invece 5
secondi nella modalit originaria, il miglioramento 5/2. Chiamiamo speedup migliorato questo valore,
che sempre maggiore di 1.

Il tempo di esecuzione con il calcolatore originario a cui viene apportato il miglioramento sar pari al tempo
impiegato dalla porzione non soggetta a miglioramento sommato al tempo della porzione che si
avvantaggia del miglioramento:

Lo Speedup complessivo il rapporto tra i tempi di esecuzione:

CAPITOLO 2

CARATTERISTICHE DELLE ISTRUZIONI DI UN CALCOLATORE


I linguaggi con cui noi riusciamo a dialogare con il processore sono stratificati. Esistono una serie di livelli
stratificati. Nello strato pi interno, quello pi vicino al processore, il linguaggio incomprensibile da un
essere umano(linguaggio macchina), invece man mano che ci si avvicina agli strati pi esterni,il linguaggio
diventa sempre pi comprensibile per l'essere umano(linguaggi di alto livello).
I linguaggi ad alto livello sono stati introdotti per codificare gli algoritmi in un modo pi semplice e intuibile
da parte dell'essere umano. Ci che viene realmente eseguito da una CPU sono le istruzioni del programma
tradotte in un linguaggio pi intuibile da parte del processore costituito da stringhe di 1 e 0(linguaggio
macchina).
Il linguaggio "appena superiore" al linguaggio macchina il linguaggio ASSEMBLER. Questo linguaggio una
traduzione 1:1 di ogni istruzione del linguaggio macchina con una sintassi pi comprensibile per luomo.
I livelli d'istruzione che operano sulla CPU fondamentalmente sono di 3 tipi:
-

Istruzioni aritmetico-logiche(somma, and logico, ecc..)


Istruzioni per accedere alla memoria(istruzioni per scrivere e leggere dalla memoria)
Lettura(LOAD)
Scrittura(STORE)
Istruzioni di controllo (o di diramazione)
Salti incondizionati(JUMP)
Salti condizionati(BRANCH)

I JUMP ad esempio possono essere confrontati con le chiamate di funzioni di qualsiasi linguaggio ad alto
livello, visto che, in questo caso, avviene un salto incondizionato da una istruzione ad un altra. Alla fine si fa
ritornare l'esecuzione all'istruzione successiva a quella chiamante.

1. Ciclo Istruzione
L'istruzione una sequenza di bit che il processore legge caricando dalla memoria. Una volta lette le
istruzioni verranno eseguite sempre una dopo l'altra. Lesecuzione di una istruzione avviene grazie ad un
ciclo, chiamato CICLO D'ISTRUZIONE.
Il Ciclo di Istruzione composto da:
-

FETCH dellistruzione: e' la fase di prelievo dell'istruzione. Questa fase avviene grazie al PROGRAM
COUNTER che contiene l'indirizzo dell'istruzione da eseguire. Questa viene prelevata e poi caricata
nel processore pronta per essere decodificata ed eseguita.

DECODE dellistruzione: ' la fase di riconoscimento dell'istruzione che avviene grazie all'unit di
controllo che legge listruzione e riconosce quale operazione bisogna eseguire. Ovviamente queste

operazioni sono state definite secondo rigide convenzioni. Ogni famiglia di processori ha delle
proprie convenzioni.
EXECUTE dell'istruzione: la fase di esecuzione dell'istruzione.

Queste fasi sono sequenziali e sono arbitrate dal clock.

2. Categorie Di Formati Istruzione


Le istruzioni si distinguono per il numero di bit di cui sono composte, inoltre possono esserci due categorie
di formato istruzione:
-

Formato a lunghezza fissa (numero di bit fisso)


Formato a lunghezza variabile (numero di bit variabile)

Il formato a lunghezza variabile prevista maggiormente nelle macchine di tipo CISC. Le macchine di questo
tipo quindi hanno bisogno di un' unit di controllo molto complessa per poter leggere ogni tipo di
lunghezza delle istruzioni. Invece le macchine di tipo RISC che hanno l'obbiettivo di semplificare l'unit di
controllo, prevedono un formato d'istruzione a lunghezza fissa.

3. Struttura Dell'istruzione
Un istruzione costituita essenzialmente dal codice operativo (OPCODE): tra i bit che compongono
l'istruzione ce ne saranno un certo numero che servono a specificare l'operazione dell'istruzione corrente. Il
codice operativo formato da log2n bit, arrotondato per eccesso, dove n il numero delle istruzioni
possibili. Ad esempio se ho bisogno di implementare un processore che necessita di 100 tipi di istruzioni,
servirebbero per eccesso 7 bit per codificare il codice operativo di ogni istruzione, che pu definire fino a
128 istruzioni (27). In genere i 28 tipi di istruzione rimanenti non vengono implementate proprio per non
incorrere in complicanze dell'unit di controllo. Per queste istruzioni rimanenti, in futuro, potrebbero
essere utilizzate per delle nuove frontiere tecnologiche, in modo tale da poter effettuare un semplice
upgrade invece di riprogettare tutta l'unit di controllo da zero.
Oltre ai bit del codice operativo ci sono inoltre delle sequenze di bit che identificano gli operandi su cui
listruzione effettua le operazioni.

CAPITOLO 3

CLASSIFICAZIONE DEI PROCESSORI


Si possono caratterizzare i processori in base a diversi aspetti.
1. Classificazione per istruzione
Un aspetto la tipologia di lunghezza fissa o variabile delle istruzioni affrontata nel capitolo precedente: le
istruzioni a lunghezza fissa tipiche di una architettura con filosofia RISC e istruzioni con lunghezza variabile
tipiche di una architettura con filosofia CISC. Inoltre le istruzioni fondamentalmente si dividono in tre
famiglie:
o

istruzioni di accesso a memoria

istruzioni che modificano il flusso del programma

istruzioni che codificano operazioni logico-aritmetico

2. Classificazione per parola


Un altro aspetto con cui possibile caratterizzare i processori attraverso il tipo di dati che essi trattano. Ci
sono una serie di dati che si differenziano sia per il formato(numero bit) e sia per il tipo di dati che questo
formato va a interpretare. Ad esempio i dati di tipo numerico si codificano con o senza la virgola(interi o
reali) e questo presuppone una codifica in binario puro con o senza segno o con una codifica che tiene
conto di potenze di due sia positive e sia negative in modo da poter compattare i numeri in diversi formati
come anche la virgola fissa o mobile. Standard pi efficienti prevedono luso della virgola mobile( vedi
floating point). Bisogna precisare quindi se il processore elabora dati in floating point o in virgola fissa. Anni
fa non tutti i processori elaboravano dati floating point poich non prevedevano al loro interno hardware
sufficiente per effettuare queste operazioni, e quando era necessario fare calcoli in virgola mobile questi
venivano eseguiti da un software. Se cera bisogno di fare molti di questi calcoli si comprava il coprocessore matematico che implementava al suo interno in hardware questo tipo di calcoli. Ora possibile
integrare nello stesso chip degli hardware per effettuare calcoli matematici in floating point, infatti la
maggior parte dei processori del giorno doggi gestiscono dati in floating point, contengono al loro interno
un set istruzioni che permette la gestione di questi. Per anni questo non stato possibile poich i
processori, avendo un hardware limitato, avevano a disposizione quindi un set di istruzioni che prevedeva
la gestione dei soli numeri interi.
Una volta scelta la possibilit di utilizzare dati piuttosto che altri(floating point piuttosto che numeri interi)
bisogno stabilire la grandezza di questi dati, di quanti bit devo essere questi dati. I processori dunque si
differenziano per i tipi di dati che possono gestire e la loro dimensione, dati da 4 byte intero, 8 byte intero,
16 byte intero oppure 32bit floating point(singola precisione) o 64bit floating point e cos via.
Un ulteriore aspetto che differenzia i processori rappresentato anche della profondit della parola.
Lunit il bit che viene elevato a byte(1byte = 8bit) con 1 byte posso esprimere informazioni in un range
tra 0 e 255(28). Se linformazione che voglio descrivere rappresentabile con un singolo byte sufficiente la
9

parola di un byte, ad esempio le lettere dellalfabeto, 26 lettere entrano in un byte(256 lettere) posso
ancora codificare le lettere maiuscole e minuscole, numeri, simboli di punteggiatura. Con un byte si
possono benissimo rappresentare caratteri alfanumerici e attraverso una codifica(in questo caso la codifica
ASCII) a associa ad un numero tra 0 e 255 un simbolo. Quel byte invece non pi sufficiente per indicare
quanti sono gli spettatori al concerto di Vasco Rossi. Non pi sufficiente perch non riesco a esprimere
con un byte questa informazione. Bisogna quindi definire una quantit che verr usata come riferimento
standard dellarchitettura: la parola (WORD). La parola diviene lunit. Un architettura definisce una parola
con un certo numero di byte che tratta come nucleo del dato pi frequentemente adoperato. Le parole
solitamente sono di 4byte, tutto il traffico nel processore avviene usando dati di 4 byte. I dati sono nel
formato definito dalla WORD.

3. Classificazione per la gestione della memoria interna


Possiamo differenziare i processori anche in base a come i dati allinterno di questo vengono gestiti. La
macchina secondo Von Neumann strutturata con una cpu, una memoria centrale, periferiche di
input/output e i canali di comunicazione tra questi. Si pu immaginare ad un modello in cui il processore
quando ha bisogno di un dato in una istruzione lo va a reperire ogni volta direttamente dalla memoria
centrale e il risultato lo scrive sempre in memoria. Ma si pu pensare ad un processore che abbia al suo
interno unarea di memoria in cui i dati quando servono vengono letti dalla memoria centrale scritti in
questa area e ogni volta che servono vengono letti da questa e non dalla memoria centrale, purch al
termine di tutto questi dati vengano scritti in memoria centrale.
Sono stati sviluppati tre modelli per la gestione per questa memoria intima del processore.

10

1. Una soluzione semplice era il processore ad accumulatore, che prevedeva al suo interno una sola
area di memoria, laccumulatore, utile a contenere un solo dato.

Nellaccumulatore veniva caricato un operando dalla memoria e quando il processore doveva


effettuare delle operazioni, che di solito necessitano di due operandi sorgente, ad esempio la
11

somma (SUM), listruzione intendeva fare la somma di due operandi, uno letto dalla memoria e
laltro quello che risiede nellaccumulatore. Il risultato andava scritto sempre nellaccumulatore.
Le istruzioni nella loro strutturazione sono semplici in quanto sono costituiti dal codice operativo e
un indirizzo di memoria.
SUM

2730

Loperando nellaccumulatore veniva sommato alloperando che si trovava nellindirizzo di memoria


2730, e il risultato veniva scritto nellaccumulatore. Dopo loperazione quello che cera prima
nellaccumulatore veniva perso, cera il risultato della somma.
Per scrivere il risultato nella memoria centrale ad un certo indirizzo:
STORE

3256

Il valore nellaccumulatore veniva scritto in memoria allindirizzo 3256.


Esercizio:
Supponiamo di avere degli indirizzi di memoria A B C D e di voler mettere nellindirizzo di memoria
E la somma di quello che c nellindirizzo A e nellindirizzo B: E=A+B; e invece in F: F = A*C+D

Istruzione
LOAD A
SUM B
STORE E
LOAD A
MULT C
SUM D
STORE F

Valori nell
accumulatore
A
A+B
A+B
A
A*C
A*C+D
A*C+D

2. Un'altra tipologia di processore era quella a stack, processori a stack. In questi non c una sola
area di memoria, ma una struttura di memoria con pi locazioni a cui non si pu accedere in
maniera casuale, ma si pu aver accesso solo a chi in testa allo stack(come una pila LIFO Last
Input First Output), lultimo dato inserito il primo ad essere prelevato. C un meccanismo che
prevede uno STACK POINTER (SP) che mi indica larea dello stack a cui posso accedere, e che
contiene quindi il dato in questione. Nelle operazioni aritmetico logiche non bisogna specificare
nellistruzione lindirizzo degli operandi, perch questi sono i primi due che si trovano al top dello
stack e sono questi che verranno utilizzati nelle operazioni. Le operazioni di tipo LOAD vengono
chiamate in questo contesto push, mentre quelle di STORE pop

12

Bisogna per stabilire delle convenzioni sullo stack pointer: lo stack pointer punta alla prima vuota
o allultima piena. Lo stack pointer pu puntare alla prima area dello stack vuota o allultima piena.
Ipotizzando che SP punti alla prima vuota.

LOAD (push):
M[indirizzo] ->S[SP]
SP+1->SP
Letto il dato dalla memoria(indirizzo) viene memorizzo nello stack, nellarea di questo puntata dallo
stack pointer(questa area dello stack vuota visto che SP indica proprio larea vuota per la
convenzione adottata). Bisogna per incrementare SP per farlo puntare nuovamente alla prima
vuota.
STORE (pop):
SP-1->SP
S[SP]->M[indirizzo]
SP punta alla prima vuota(garbage, valore dellultimo dato scritto e non ha nessun valore utile),
bisogna farlo puntare allultima piena e poi scrivere ci che puntato dallo stack pointer in
memoria(nellindirizzo di memoria).
Utilizzando invece la convenzione in cui SP punta allultima piena.

13

LOAD (push):
SP+1->SP
M[indirizzo] -> S[SP]
Bisogna far puntare prima SP alla prima vuota(altrimenti perdo il dato che avevo in precedenza), e
caricare poi il dato dalla memoria.
STORE (pop):
S[SP]->M[indirizzo]
SP-1->SP
Lultimo pieno (quello puntato da SP) proprio il dato da salvare in memoria. Per adesso bisogna
decrementare lo stack pointer.
E essenziale stabilire la convenzione. Nella strutturazione pipeline(che vedremo successivamente
nel corso) si prevede che prima si eseguono dei calcoli e poi si accede a memoria. Potrebbe essere
utile questa scelta per stabilire quale convenzione usare per lo stack pointer, per le due
convenzioni non vanno comunque bene(perch nella LOAD di una convenzione si eseguono prima
le operazioni e poi si accede a memoria ma nella STORE il contrario, e viceversa con laltra
convenzione), allora si pu ipotizzare di fare una ibridazione e scegliere di eseguire le load secondo
una convenzione e le store secondo un'altra, ma non va bene comunque. Bisogna scegliere una di
queste due convenzioni in base a quale operazione faccio pi frequentemente load o store.
Intuitivamente si portati a dire che le load e le store sono effettuate lo stesso numero di volte,
per le load sono pi numerose, perch si sale lungo lo stack con le load e si scende con le store ma
anche con le operazioni che prevedono la memorizzazione di un operando nella memoria.
3. Un'altra famiglia di processori quella dei processori a registri, che utilizzano un certo numero di
locazioni (come lo stack) a cui per si pu accedere a piacere, si pu accedere a qualsiasi locazione
senza vincoli. Queste celle assumono tutte la stessa dignit. C un area di memoria costituita da
dei registri che si indirizzano autonomamente. Non si legati alla cima della pila. Per questa
comodit si paga sia un hardware pi complesso per gestire questo sistema, e soprattutto si paga
un discorso che obbliga a inserire nellistruzione la specifica degli indirizzi degli operandi.

14

Nel caso del processore ad accumulatore un operando era sempre laccumulatore quindi
nellistruzione si indicava un solo indirizzo per loperando da prelevare dalla memoria; nel
processore a stack gli operandi erano sempre i primi due dello stack e nelle istruzioni non si
specificava nessun indirizzo. Nei processori a registri invece necessario specificare il primo
operando(che si trova in un determinato registro), il secondo operando(che si trova in un altro
registro), il terzo operando(che si trova in unaltro registro ancora). Quindi listruzione oltre al
codice operativo prevede tre campi per gli operandi, e questi campi contengono un numero di bit
pari al log2(num. registri). Si pu sempre pensare di dare una ulteriore flessibilit e oltre a prelevare
gli operandi dai registri si vuole dare la possibilit di prendere operandi dalla memoria, ad esempio
si vuole effettuare la somma di due dati in cui uno si trova in un registro, mentre il secondo si trova
in una certa locazione della memoria centrale(indirizzo di memoria). Oppure la locazione dellarea
di memoria di un operando scritta addirittura in un registro. In questi casi il formato si complica
perch il numero dei bit dei campi per la specifica degli operandi deve poter contenere anche gli
indirizzi di memoria, bisogna prevedere la gestione di questi meccanismi.

La maggior parte dei processore di oggi sono della tipologia a registri. Tipicamente le macchine a registro si
suddividono in LOAD/STORE piuttosto che no. Una macchina a registro si dice che una macchina
LOAD/STORE nel momento in cui le uniche istruzioni di questa macchina che fanno riferimento alla
memoria sono solo le istruzioni LOAD e STORE, significa che nellesecuzione di una istruzione aritmetica, la
somma per esempio, il processore non pu accedere alla memoria, gli operandi devono essere stati gi tutti
preventivamente caricati nei registri. Non possibile con questa tipologia di macchina prendere un
operando dalla memoria direttamente con la istruzione aritmetico logica, SUM per esempio, passando
lindirizzo della locazione di memoria, ma tutti gli operandi sono stati gi tutti preventivamente caricati nei
registri, e il risultato di questa operazione deve essere ancora scritto in un registro, non pu essere scritto
in una locazione di memoria. Nelle macchine di tipo LOAD/STORE le uniche operazioni che permettono di
caricare o scrivere da/in memoria sono solo e soltanto le operazioni di LOAD e di STORE. Osserviamo nella
figura che quando c da usare lALU la memoria scollegata

15

4. Classificazione per modi di indirizzamento


Un altro modo di caratterizzare i processori il modo con cui questi gestiscono laccesso a memoria, cio il
modo con cui lindirizzo, dove andare a leggere o scrivere, pu essere fornito al processore. Il modo pi
semplice scrivere un indirizzo nellistruzione stessa(modalit con indirizzo immediato); un altro modo
quello con indirizzo diretto dove lindirizzo dell area di memoria scritto in un registro. Lindirizzo dellarea
di memoria va prelevato da un registro.

16

CAPITOLO 4

MODI DI INDIRIZZAMENTO
Nel capitolo precedente abbiamo distinto le macchine a registri in due categorie:
1. Larchitettura Load-Store che consente di fare operazioni di tipo aritmetico logico solo fra operandi
che sono nei registri, quindi non si pu fare la somma fra un dato che in un registro e un dato che
in memoria, o ancor peggio la somma di due dati che sono in memoria. Bisogna solo fare
operazioni aritmetico logiche che riguardano due operandi che sono gi presenti nei registri. In
sostanza si acceder alla memoria solo attraverso operazioni tipiche della memoria. Quindi questo
tipo di macchina sar una macchina a registri LOAD-STORE per significare che la memoria sar
usata solo con istruzioni di tipo LOAD STORE. Quindi per fare un operazione aritmetica fra due dati
che sono in memoria prima bisogna eseguire la LOAD di questi due dati , per esempio prendere il
primo dato e inserirlo nel registro R1 , leggere un secondo dato e inserirlo nel registro R2 e solo ora
possibile fare le operazioni fra R1 ed R2; il risultato andr in un altro registro che pu anche
essere uno dei due che si appena usato, ma non si pu scrivere il dato in memoria se non
attraverso loperazione di STORE
2. Larchitettura Registro-Memoria invece prevede di usare lALU per fare calcoli ma nellambito della
stessa istruzione anche di accedere a memoria. Questo tipo di struttura per preveder un codice
operativo che dovr contenere sia la specifica di un registro (per loperando) sia la specifica di un
registro del risultato e sia un indirizzo. Quindi siccome per indirizzare un registro tipicamente
bastano pochi bit, con 32 registri sufficiente un insieme di 5 bit. Con 5 bit infatti si possono
codificare numeri compresi tra 0 e 31; scrivendo il numero 7 si intende il registro R7. Per specificare
un indirizzo non bastano 5 bit, attraverso 5 bit si indirizza solo una porzione della memoria.
Tipicamente per specificare un indirizzo si necessitano di pi bit, almeno che per specificare un
indirizzo non si utilizza un artefatto: scrivere lindirizzo di memoria in un registro. Quindi
nellistruzione si andr solamente a specificare lindirizzo del registro, che contiene lindirizzo di
memoria, attraverso i 5 bit.

1. Indirizzamento Diretto
La modalit di indirizzamento appena mostrata si chiama INDIRIZZAMENTO DIRETTO. C un registro che
direttamente contiene lindirizzo dellarea di memoria. Nellistruzione si andr a specificare solamente
lindirizzo del registro che contiene lindirizzo di memoria a cui accedere.
2. Indirizzamento con Immediato
possibile scrivere lindirizzo direttamente nellistruzione stessa. Quindi listruzione dovr contenere oltre
anche varie altre informazioni fra cui una di queste lindirizzo.

17

3. Indirizzamento con Scostamento (displaycement)


Lindirizzo con displaycement viene calcolato sommando al contenuto di un registro un numero (INDIRIZZO
BASE) codificato nellistruzione stessa. Nellistruzione verr specificato quindi un registro per esempio R9 e
un immediato(indirizzo base), e lindirizzo di memoria verr calcolato sommando questo indirizzo base che
e il contenuto di R9. Ci si pu chiedere, ma non era meglio inserire tutto limmediato di per s? Si per se
questa istruzione va eseguita allinterno di un loop e ad ogni ciclo si ha la necessit di accedere a un dato
diverso bisogna avere un indirizzo diverso. Con lindirizzamento con scostamento listruzione contiene una
un immediato e un registro da sommare cos che nel loop ogni volta verr cambiato il valore dellimmediato
Ci sono anche altre modalit che sono usate nellambito dei programmi in maniera diversa. Cio quando un
programma usa una modalit di indirizzamento si pu vedere se questa modalit frequente perch in tal
caso si pu pensare di potenziare lesecuzione di un istruzione che usa quel tipo di modalit di
indirizzamento.

Questo diagramma prende in considerazione 5 modalit diverse di indirizzamento:


1. Indiretto in memoria
2. Scalato
3. Indiretto con registro
4. Immediato
5. Scostamento
e per ciascuna di queste usando 3 programmi standard che vengono considerati come benchmark, si pu
vedere, quando si esegue una compilazione, come funziona luso di tali indirizzamenti. Dopodich se si
vuole provvedere a come potenziare larchitettura di un sistema che dovrebbe essere usato
prevalentemente per fare calcolo scientifico si studia il comportamento di quello che fa il sistema quando
usa un programma di tipo scientifico come SPICE, e si scopre che oltre la met degli accessi in memoria
avvengono attraverso uno SCOSTAMENTO (displaycement). Questo il tipico modo con cui si accede ai dati
dei vettori. Quindi capiamo bene come in SPICE questo il modo pi frequente perch quando si fa una
18

simulazione di una rete elettrica si risolvono sistemi che fanno uso di calcolo vettoriale e per questo per
accedere a elementi di un vettore si ha bisogno di accedere con indirizzamenti codificati in questo modo.
Per cui se si vuole migliorare qualcosa di questo processore, relativamente agli accessi in memoria
converr migliorare il modo con cui il processore accede a memoria dopo aver calcolato lindirizzo in
maniera di spiazzamento.
Se invece si vuole realizzare un sistema che fa tipicamente editoria, (TeX) ci si accorge che quando il
compilatore accede a memoria utilizza indirizzi immediati cio nella esecuzione si fa riferimento a un
indirizzo codificato in maniera diretta nellistruzione stessa.
Invece lindirizzamento che prende il nome di REGISTRO INDIRETTO un indirizzamento secondo il quale il
registro contiene un indirizzo che non lindirizzo del dato di destinazione ma ancora un altro indirizzo e
quindi si va due volte in memoria.
4. Indirizzamento con registro auto-incrementato o auto-decrementato
In questo diagramma non presente un altro modo abbastanza frequente di accedere alla memoria che
quello con auto-incremento o auto-decremento del registro, in cui listruzione di accesso a memoria
ingloba in s anche loperazione di aggiungere o togliere una quantit al registro usato per conoscere
lindirizzo. Ad esempio in R8 c un certo indirizzo, si pu avere o meno limmediato da sommare al registro
per avere lindirizzo di memoria; si effettua la somma nel caso sia necessario fra limmediato e il registro, e
si ottiene lindirizzo di memoria. Oltre a questo il registro viene aumentato o diminuito di 4 o comunque di
una quantit che rappresenta il numero di byte letti dalla memoria in maniera tale che questo registro in
automatico adesso punter al prossimo dato. C la operazione con indirizzamento auto incrementante o
auto decrementante prefisso a significare che prima verr incrementato o decrementato il registro e poi
verr usato per accedere in memoria.
Questo meccanismo utile per la gestione di uno stack, non si intende lo stack interno al processore, la
macchina sempre a registri, ma si intende uno stack come un area di memoria che il processore gestisce.
Valgono le due convenzioni su SP che punta alla prima vuota o allultima piena.
Ad esempio vediamo la gestione dello stack. D la dimensione della cella dello stack, ed R6 il registro su
cui avvengono le operazioni.
Consideriamo il caso in cui SP punti allultima piena (1).
POP (1):
M[SP] R6
SP D SP

PUSH (1):
SP + D SP
R6 M[SP]

19

Consideriamo il caso in cui SP punti allultima vuota (2).


POP (2):
SP D SP
M[SP] R6

PUSH (2):
R6 M[SP]
SP + D SP

Supponiamo che R31 il registro che contiene lo stack pointer che un indirizzo di memoria. Posso fare
uso dellindirizzamento con auto-incremento o auto-decremento, si scrive un +( o un -) a destra o a
sinistra del registro per indicare rispettivamente un incremento( o decremento) postfisso o prefisso.
Le istruzioni scritte prima diventano:
POP (1):
LOAD

R6

R31-

Scrivo in R6 il dato letto dalla memoria puntata da R31. Il segno indica che dopo aver scritto nel registro
R6, R31 viene decrementato: R31 = R31 D
PUSH (1)
STORE

R6

+R31

R1 viene prima incrementato, e poi viene scritto nellarea di memoria puntata il valore di R6.
Con laltra convenzione
POP (2):
LOAD

R6

-R31

R6

R31+

PUSH (2):
STORE

E importante precisare che quando si accede a uno stack con le operazioni di PUSH e POP, bisogna prima
verificare che lo stack non sia pieno, altrimenti si scrive su una zona di memoria che magari stata riservata
per contenere altri dati; e che non sia vuoto.
Ipotizziamo che R30 e R29 sono due registri che contengono rispettivamente R30 il TOP (ultima locazione
accessibile) ed R29 il BOTTOM (la prima cella accessibile).

20

Con la convenzione dellultima piena, per effettuare una push bisogna verificare che:
SP < TOP
Invece per effettuare una pop bisogna verificare che:
SP >= BOTTOM
Con la convenzione dellultima vuota invece, per effettuare una push deve essere verificato che:
SP <= TOP
Invece per effettuare una pop:
SP > BOTTOM
Tipicamente lindirizzamento con spiazzamento (pi usato nel calcolo scientifico) ha questa struttura:
Il codice operativo ad indicare un operazione di LOAD o STORE, e un registro destinazione o sorgente: nel
caso di una LOAD il registro di destinazione, nel registro verr scritto il risultato; altrimenti nel caso di una
STORE il registro sorgente, contiene un dato che verr scritto in memoria. Infine ci c un altro registro
che contiene lo spiazzamento e un indirizzo per limmediato, questi ultimi sommati vanno a formare
lindirizzo di memoria. Questo immediato da prendere come indirizzo a cui sommare il contenuto del
registro di spiazzamento per ottenere lindirizzo finito. E normale che questo immediato ha un numero di
bit che minore del numero di bit dellistruzione. Se considero un processore con N istruzioni, sono
necessari almeno Log2N bit per il codice operativo. Ad esempio 6 bit per il codice operativo. Considerando
inoltre un processore con 32 registri, sono necessari 5 bit per specificare un registro. Nella struttura fornita
prima dellistruzione in cui compaiono oltre al codice operativo due registri e considerando ancora che le
istruzioni sono di 32 bit rimangono per limmediato solamente 16 bit. Tipicamente uno spazio di
indirizzamento nella memoria, ha la dimensione del BUS del processore. Quindi se il processore traffica con
dati da 32 bit anche gli indirizzi saranno di 32 bit perch allinterno del processore gli indirizzi che si
possono gestire (per esempio somma tra immediato e registro di spiazzamento) sono indirizzi scritti in un
registro. Quindi non si vincolati ad avere una memoria solo di 232 byte = 4 gigabyte, significa per che in
un momento storico della vita di quel programma ci sar una memoria di 4 gigabyte. Molto probabilmente
la memoria sar effettivamente di diversi terabyte, per si sta facendo riferimento a uno spazio di
indirizzamento che una fetta(4 gigabyte) di questa memoria. Per cui quando si accede a memoria, questo
indirizzo sar un indirizzo che andr a consultare un SUBSET della memoria di 4 gigabyte.
Allora questo immediato di 16 bit (ad esempio) ipotizzando 32 bit per questa istruzione, va utilizzato come
se fosse di 32. Quindi questi immediati vengono tipicamente espansi a 32 bit. A partire da questo numero
di 16 bit verr creato un numero di 32 bit facendo un espansione in segno, perch questo numero pu
essere positivo o negativo ad indicare uno spostamento in avanti o indietro rispetto al registro di
spiazzamento. Se positivo o negativo lespansione in segno preveder la replica dei bit pi significativi.
Dato un numero di N bit e lo si vuole espandere a N+K bit, si andranno ad aggiungere K bit alla sua sinistra.

21

Esempio: consideriamo il numero 18 e lo si vuole scrivere con 4 cifre. Bisogner scrivere 0018, poich
questo un numero positivo si andranno ad aggiungere gli zeri a sinistra.
Se il numero invece binario con segno in complemento a 2: i numeri negativi avranno il primo bit uguale a
1. Per scrivere -7 usando 5 bit binari, si pu scrivere -7 in complemento a 2.
00111 (7) in 5 bit
11000

C.A. 1

11001

C.A. 2 (-7)

Se si vogliono usare 8 bit per rappresentare -7:


00000111 (7) in 8 bit
11111000
11111001

C.A.1
C.A. 2 (-7)

Per leggere questi risultati dovr verificare il primo bit se esso uguale a zero allora il numero in binario
puro e lo si pu leggere normalmente. Se invece il primo bit uguale a 1 non si pu legge il numero in
binario e bisogner fare un operazione che di nuovo in complemento a 2. Si scriver il segno meno e si
effettua il complemento a 2 sul numero:
11111001 -
00000110 C.A. 1
00000111

C.A. 2 (7)

Il risultato 7, con il segno negativo poich il primo bit era 1: -7.


Bisogna chiedersi per se questi 16 bit (per esempio) sono sufficienti piuttosto che no a indirizzare la
memoria. In realt con 16 bit, rispetto un valore scritto in un registro, ci si pu spostare 215 locazioni prima
o 215 locazioni dopo. Per cui se si sta spaziando su una memoria a partire dal valore puntato dal registro di
spiazzamento, sommando questo immediato positivo o negativo ci si pu muovere su 215 locazioni sopra o
215 locazioni sotto rispetto a questo registro. Questi 16 bit sono sufficienti o sono pochi?. Se per esempio
questi sono pochi ci sono dei problemi, perch bisognerebbe pensare a un istruzione che sia pi lunga di 32
bit. Allora prima di capire se questi bit sono pochi o molti, si pu fare uno studio statistico e vedere quando
si effettua un accesso a memoria questo displaycement , di solito, quanto grande? Di solito lo quanto
lungo lo spostamento richiesto? E lo stesso, a maggior ragione, quando si dovranno eseguire delle istruzioni
di salto di tipo JUMP e di tipo BRANCH i quali prevedranno un codice operativo diverso.

5. Indirizzamento per le operazioni di salto


Un istruzione JUMP effettua un salto ad un nuovo indirizzo di memoria senza nessuna condizione, non si
dovr codificare altro. Si avr a disposizione oltre al codice operativo una serie di bit per scrivere il valore di
quanto saltare.

22

Un istruzione BRANCH invece necessita di una serie di bit oltre al codice operativo per codificare i termini
delloperazione logica da fare. Ad esempio se si vuole codificare un istruzione, un branch se R2 > R6,
bisogner mettere da parte tanti bit per scrivere R2 e tanti per scrivere R6. Quindi nei branch, solitamente i
bit che restano per specificare di quanto saltare sono di meno a differenza del JUMP dove si ha a
disposizione praticamente tutta listruzione a meno del codice operativo per codificare la lunghezza del
salto. Nei branch, invece, parte dellistruzione , oltre al codice operativo, deve essere sacrificata per
codificare su quali registri si sta facendo il test se saltare o meno.
E quindi anche qui avrebbe senso studiare quanto sono lunghi solitamente i salti di un istruzione JUMP o di
una istruzione BRANCH quando si usano software come SPICE, GCC o altri. Se per esempio ci si accorge di
avere salti di 266 locazioni bisogna trovare una soluzione! Una potrebbe essere quella di utilizzare un
istruzione di 71 bit, 5 per il codice operativo e 66 per fare il salto, per poi questi 71 bit sono uno spreco
quando le istruzioni rientrano in un numero pi ridotto. Per cui uno studio senzaltro utile quello che
viene in mente nel verificare questi numeri di bit di spiazzamento.
Un istruzione CALL effettua un salto incondizionato come la Jump, per a differenza della Jump che salta
semplicemente le istruzioni, la Call effettua un salto ma con un meccanismo di salvataggio della posizione
da dove si effettuato il salto. Viene memorizzato lindirizzo da dove viene eseguito il salto. Questo il
meccanismo per gestire le procedure, le subroutine. Quando si esegue una Call, viene chiamata una
procedura, vengono eseguite le istruzioni di questa, e quando termina lesecuzione del programma ritorna
allistruzione successiva alla Call.
Pu capitare che in un codice si possono avere pi chiamate a quella subroutine, bisogna prevedere una
istruzione di Call che funziona come una Jump, ma non si limita al semplice salto, deve salvare lindirizzo
successivo dellistruzione di Call eseguita prima e che dovr essere gestito al termine della procedura
dallistruzione return. Lultima istruzione di una procedura la return che anchessa effettua un salto
incondizionato, salta allindirizzo che salvato quando stata eseguita la Call. Bisogna scrivere in un certo
registro speciale lindirizzo successivo allistruzione di Call.

Il problema di questo meccanismo nasce quando anche nella subroutine avviene un'altra chiamata ad
un'altra subroutine o quella subroutine stessa(ricorsione). Nella subroutine c una nuova Call ad un'altra
procedura che va a modificare nel registro lindirizzo di ritorno, quindi ora nel registro viene scritta la nuova
posizione dove ritornare dopo questa Call ma si perde la posizione precedente. Bisogna quindi, per ovviare
a questo problema, vincolare la subroutine a non fare altre chiamate a subroutine.
Un metodo migliore per gestire le Call/return scrivere i diversi indirizzi di ritorno in un area di memoria,
questarea di memoria, lo stack, deve essere organizzata con tipologia LIFO. Quando viene chiamata una
subroutine nello stack si va a memorizzare lindirizzo di ritorno, quando nella subroutine avviene un'altra
chiamata ad un'altra procedura viene salvato lindirizzo di ritorno nella cima dello stack, e cos via se ci sono

23

altre chiamate. Nelle operazioni di return, viene effettuato il salto allindirizzo in cima allo stack(lultimo
inserito) e cos via nelle return delle subroutine.
Possiamo avere anche 1000 registri in un processore, ma di questi se ne visualizzano 32 o 64 e gli altri
servono per gestire un meccanismo che viene chiamato Finestra di Registri. Per individuare un singolo
registro, tra 1000, avrei bisogno di 10 bit, e un istruzione che fa uso di 3 registri, avrebbe bisogno di soli 30
bit per specificare 1000 registri. Non se ne utilizzano mai 1000 registri, di solito il valore di un registro viene
sovraccaricato dopo una operazione. Accedere a 1000 registri pi lento, bisogna connettere per esempio
un dato registro allALU(con un selettore multiplexer). Il cammino critico, il percorso che deve fare un
segnale in un circuito, dalla sorgente alla destinazione in un colpo di clock, se bisogna attraversare un
numero di gate di un selettore che ha molti ingressi molto pi lungo e si impiega pi tempo rispetto ad
uno che ne ha di meno. Si possono spezzettare i cammini critici in pi tappe e avere un clock che pu
andare pi velocemente, ma la latenza quella di attraversare un sistema: cercare una cosa tra poche cose
pi veloce che cercarla tra molte.
La Jump come la Call o come la return sono salti incondizionati. I branch sono invece salti condizionati che
vengono effettuati quando sono verificate delle condizioni. Condizioni logiche su cui vengono impostati in
BRANCH. Queste condizioni da codificare sono istruzioni che possono essere calcolate molto velocemente.
Bisogna capire subito se si deve saltare una istruzione o meno. Le istruzioni di salto vanno gestite in
maniera molto pi veloce rispetto allesecuzione di un calcolo. Normalmente le istruzioni devono essere
eseguite uno dopo laltra, al termine di una viene eseguita la successiva. Per si pu pensare ad un
meccanismo in cui non si necessariamente vincolati a eseguire una istruzione dopo aver terminato la
precedente(parallelizzazione delle operazioni, pipeline). Istruzioni di questo genere possono essere iniziate
non necessariamente al termine dellistruzione precedente. Quando viene decodificata listruzione di
calcolo, questa pu durare del tempo intanto viene eseguita la successiva e nella peggiore delle ipotesi si
pu avere una dipendenza di questa istruzione con loperazione precedente. Nellistruzione di salto questo
calcolo sulla condizione deve essere fatto velocemente, perch finch non si deciso se saltare o meno non
possono essere eseguite le istruzione successive o quella nella posizione da saltare. Bisogna verificare le
condizioni nel modo pi velocemente possibile.

Set di istruzioni che codificano condizioni logiche:


EQUAL TO
NOT EQUAL TO
LESS THAN
LESS THAN OR EQUAL TO
GREATER THAN
GREATER THAN OR EQUAL TO

==
!=
<
<=
>
>=

Una politica potrebbe essere quella di non implementare Greather Than perch questa operazione
effettuata da Less Than Or Equal To. Si possono implementare diverse situazioni per semplificare le
istruzioni.

24

Osservando la figura pag. 507, stata tracciata la frequenza dove certi bit di spiazzamento sono richiesti.
Per esempio 5 bit di spiazzamento nel 10 % dei casi e via dicendo. Il grafico arriva fino a 16 perch i
programmi alla fine considerano le istruzioni di salto realmente implementate solo con 16 bit. Questo non
significa che si possono fare dei salti da una locazione a N locazioni pi in l dove log2N superiore a 16.
Una volta affidato al campo immediato un numero di bit , 16 come abbiamo detto, come si risolve il
problema di saltare invece che di 215 locazioni in avanti (16 significa 215 locazioni pi avanti e 215 locazioni
indietro) ma di 228 locazioni pi in l ad esempio? Servirebbero altri 13 bit che non possono essere inseriti
perch non si posso fare istruzioni pi lunghe. Per risolvere questo problema possiamo pensare allatletica!
Se si vogliono saltare pi di 8 metri e mezzo bisogna fare un salto triplo! Quindi qualora serve realmente
spostarsi di 228 locazioni pi in l, bisogna fare un salto di 215 locazioni, e inserire nellistruzione 215 un'altra
istruzione di salto fino alla locazione di destinazione. Questultima istruzione di salto non condizionata.
Perch la condizione stata verificata, se era necessaria, nella prima istruzione di salto(quella di 215).
Essendo una jump questultima istruzione di salto potrebbe avere uno spiazzamento per indicare il salto ad
esempio di 26 bit. Quindi sufficiente nellistruzione di salto che vuole saltare di 228 locazioni pi in l, fare
un salto di 215 e scrivere in questultima locazione un istruzione di salto anche incondizionato che ci proietta
in un altro punto dellesecuzione. Nasce un secondo problema con questa soluzione, a seconda istruzione
di salto che inserita (dopo le 215 locazioni) viene raggiunta non solo dal primo salto ma potrebbe essere
raggiunta anche con il normale corso delle istruzioni (passo passo). Prima o poi verr raggiunta listruzione
questa istruzione di salto che effettuer un salto pi avanti senza volerlo. Si dovr inserire un altro jump,
esattamente prima dellistruzione di salto in questione , che salti questultima e quindi evitandola e che ci
riporti esattamente allistruzione successiva in modo da permettere la regolare esecuzione delle istruzioni.

Questo un altro studio che invece prende in considerazione le istruzioni, le divide in due categorie: le
LOAD e le operazioni della ALU. Verifica pi o meno com landamento relativamente a un programma che
fa uso sia di numeri floating point che ad aritmetica intera perch quando studieremo il processore
ampliando lorizzonte alla parte del processore che opera sui dati floating point ci accorgeremo che il
processore pu essere diviso in due moduli:
-

Uno che opera sulle istruzioni che fanno riferimento a dati interi

Uno che opera sulle istruzioni che fanno riferimento a dati floating point

Le parti di logica che fanno i calcoli sono diverse, lhardware che permette di eseguire la somma di due
interi non la stessa che permette la somma di due floating point. La somma floating point prevede un
confronto fra le mantisse, lallineamento ecc. ecc. . Per cui lhardware fisicamente distinto allora
prendendo in esame le istruzioni che hanno a che fare con le operazioni floating point e le istruzioni che
hanno a che fare con i dati ad aritmetica fissa vediamo questo tipo di statistica di un benchmark che fa uso

25

di floating point. Se si osserva la figura, e ci si focalizza sulle operazioni che hanno a che fare con i dati ad
aritmetica intera, un quarto sono operazioni aritmetico logico.
Quando si effettua una LOAD, il 23% di queste usano un dato immediato che per esempio l indirizzo.
Nelle operazioni ALU, il 25% usano un dato immediato, quindi loperazione che prevede due operandi: un
operando un registro, laltro non in un registro ma in un immediato. Il compilatore quando ha tradotto
quellistruzione gi sapeva qualera un operando, non doveva prenderlo da un registro. Ad esempio
tipicamente in un contatore di un loop ,i++, non ha senso mettere 1 in un registro, ad esempio R6, mettere
i in un altro registro, ad esempio R7, e ogni volta fare R7+R6 cos occupo un registro R6. Sapendo invece
che a R7 va sommato sempre 1, il compilatore nello scrivere quellistruzione scriver unistruzione di
somma con immediato, quindi scriver nellistruzione stessa il numero 1 come operando immediato ed R6.
Quindi scriver unistruzione che fatta da un codice operativo che non significa somma ma significa
somma con immediato. E poi ci sar per esempio R6, e poi limmediato 1, a significare che R6 va sommato
al numero 1 e il risultato va sempre in R6.

6. Operazioni Nellinsieme Di Istruzioni


Abbiamo accennato che i dati sono anchessi nel loro formato una caratteristica che diversifica il
processore. Quindi per esempio abbiamo dati che a partire dal singolo byte possono essere raggruppati in
categorie che comprendono le word da 4 byte e le doppie word da 8 byte. Esiste anche una via di mezzo le
half word da 16 byte. Questi dati vengono effettivamente usati. Osservando la figura pag. 510, per quanto
riguarda i dati floating point, la gran parte delle operazioni riguarda dati floating point di tipo doppia word
per una certa applicazione. Questo studio serve a definire se importante considerare un ALU a 64 bit
perch per esempio se si fa in modo di trattare i dati da 64 bit come dati da 32 bit si pu pensare ad una
ALU da 32 bit(prima fa il calcolo su 32 della parola e poi sugli altri 32 bit). Se invece si deve fare la somma di
due dati da 64 bit ma lALU da 32 bit si pu decidere che tutte le volte bisogna fare somme di dati da 64
bit si divide il dato in due parti da 32 e 32, e si esegue la somma di una parte e poi dellaltra e precisamente
si far la somma ovviamente prima delle due parti meno significative. Questa somma eventualmente
generer un riporto che si aggiunger alla somma delle due parti pi significative. Avendo un ALU di 32 bit
necessario eseguire in pratica due calcoli. Questa soluzione ottima fintantoch mi accorgo che dati da 64
bit con cui fare i calcoli non sono molti. Purtroppo si nota dalla figura che nel 60 % dei casi bisogna fare
calcoli con dati da 64 bit, allora ci si pone la domanda se ha senso progettare un ALU da 64 bit per fare in un
colpo solo quello che si effettuava in due colpi. Si ha senso.
Le stesse considerazioni fatte per lALU si possono fare per i registri: devono essere di 32 bit o 64 bit? Se si
pensa a una aritmetica(ALU) di 32 bit che alloccorrenza fa anche calcoli da 64 bit, i registri saranno di 32 bit
e quando si ha bisogno di dati da 64 bit verranno utilizzati due registri contigui. Ma questa coppia di registri
pu essere R7 e R17 per esempio? Teoricamente si, per comporta dei problemi;
Nellistruzione bisogner specificare R7 e R17, che contengono le due parti di un dato, poi si avr bisogno di
specificare altri due registri che contengono le parti del secondo dato, esempio R5 ed R19, ed infine si
bisogner specificare i due registri per contenere le due parti del risultato, R8 e R21 ad esempio. Sono
necessari 6 slot per indicare gli operandi. Se i registri sono contigui invece baster specificare R7, perch il
secondo pezzo sar R8; baster specificare R9, perch la seconda parte si trova in R10; ed infine per il
risultato baster specificare R2 perch la seconda parte sar R3.
26

Un altro problema che si verifica utilizzando registri non contigui nasce quando bisogna connettere alla
parte che fa i calcoli, i due registri. Lunit di controllo diviene molto complessa.
Quindi a fronte di una massima flessibilit, che porterebbe a desiderare la libert di mettere quei 64 bit in
due registri a piacere, le cose si complicano sia dal punto di vista delle connessioni verso le unit di calcolo
e soprattutto nella strutturazione di un istruzione che dovr prevedere 6 specifiche.

27

28

CAPITOLO 5

ISTRUZIONI DEL PROCESSORE MIPS


Il processore di riferimento il processore MIPS. sviluppato con filosofia RISC, ha un numero ridotto di
istruzioni ma che hanno tutte una struttura molto simile tra di loro. Inoltre un processore di tipo LoadStore, le uniche operazioni che possono accedere a memoria sono le istruzioni LOAD e STORE.
Il processore MIPS costituito da 32 registri interi da 64bit di utilizzo generale (GPR, general-purpos
register), denominati R0, R1,, R31. Ci sono inoltre registri per numeri in virgola mobile (FPR, floating-point
register), denominati F0,F1,,F31. E infine c anche un piccolo numero di registri speciali.
I tipi di dati presenti sono: byte (8bit), half word (16bit), word (32bit) e double word (64bit).
Esistono solo due modalit di indirizzamento, immediato e con scostamento.
Le istruzioni di questo processore sono di 32 bit e presentano tutte un codice operativo costituito da 6 bit.
Le istruzioni sono fondamentalmente di tre tipi: tipo R, tipo I e tipo J.
1.

Istruzioni di tipo J

Le istruzioni di tipo J prevedono una struttura semplice, oltre ai 6 bit del codice operativo, i rimanenti 26 bit
codificano un immediato, lindirizzo dove saltare. Larchitettura prevede indirizzi di 32 bit, quei 26 bit non
sono un indirizzo assoluto, ma sono un offset ovvero uno spiazzamento che va sommato al Program
Counter (PC).
Oltre ai comuni registri di un processore, ne esistono degli altri, chiamati registri speciali o dedicati perch
non contengono dati generici, ma dati che hanno un determinato significato. Uno di questi il PROGRAM
COUNTER (PC). Il PC un registro che contiene lindirizzo dellistruzione da eseguire. Quando viene eseguita
una istruzione serve sapere lindirizzo di questa per prelevarla e il processore conosce questo indirizzo
attraverso il PC. Quando termina una istruzione e se ne deve prelevare un'altra, il PC contiene listruzione
successiva a quella eseguita grazie a un aggiornamento che viene fatto durante lesecuzione di una
istruzione. Se PC=16byte, viene eseguita listruzione al byte 16, e considerando istruzioni istruzione di 4
byte (32bit), al termine di questa istruzione, viene incrementato il PC, PC=PC+4=16+4 = 20byte, verr
eseguita listruzione al byte 20. Durante lesecuzione di una istruzione oltre a fare quello che listruzione
richiede bisogna aggiornare il Program Counter. Necessariamente ogni volta che verr eseguita una
istruzione oltre a essere compiuto quello che listruzione richiede bisogna aggiornare il PC. Se le istruzioni
hanno tutte la stessa lunghezza, lunghezza fissa, loperazione facilitata. Un conto aggiornare il PC senza
sapere dove listruzione successiva, perch quella in corso ora ad esempio di 8 byte, e quindi bisogna
aggiornare di 8byte questa volta il PC, mentre listruzione successiva magari di 15 byte e quindi bisogner
aggiornare di 12 byte il PC.
Ritornando allistruzione di salto, questa scrive un nuovo valore nel PC, scrive il valore del nuovo indirizzo
dove saltare nel PC. Listruzione non pu contenere tutti i 32 bit dellindirizzo dove saltare perch se ne
hanno a disposizione al massimo 26 di bit nel caso di una jump. I 26 bit contengono uno spiazzamento di
quanto bisogna spostarsi dallistruzione attuale, dal PC. Ad esempio se listruzione corrente si trova
allindirizzo 90 e bisogna saltare allistruzione 150, bisogner scrivere nei 26 bit il valore 60, scopriremo che
bisogner scrivere 56 perch si dar per scontato che il PC si gi incrementato di 4 byte. I salti che
29

vengono effettuati sono tutti multipli di 4 gli indirizzi si spostano di 4 in 4, visto che le istruzioni sono di 4
byte. I multipli di 4 in binario terminano con 2 zeri, gli ultimi due bit valgono zero. Si pu pensare che quei
26 bit contengono lindirizzo privato dei due zeri finali(risparmio 2 bit): 12 = 1100 => 11 = 3, scrivendo 3 (11)
ci si sta riferendo a 12(1100). Si hanno a disposizione quindi 28bit. Inoltre se il valore positivo il salto
avviene in avanti, altrimenti indietro.

2. Istruzioni di tipo R
Le istruzioni di tipo R sono istruzioni che prevedono luso di 3 operandi di tipo registro, due operandi sono
di input e 1 operando di output: due sorgenti e una destinazione. Ricordiamo che questa una macchina
Load-Store in cui le uniche operazioni che accedono a memoria sono le Load e le Store, mentre le
operazioni aritmetiche non accedono a memoria. Larchitettura ha un certo numero di registri, ma la
visibilit solo su 32 registri, gli altri servono per la Finestra di Registro. Sono necessari allora 5 bit per
codificare un registro, poich listruzione specifica 3 registri, servono 15 bit per i registri, in sostanza 6 + 3*5
= 21, gli altri 11 bit sono utilizzati per altre informazioni, se i dati sono con segno senza segno.

3. Istruzioni di tipo I
Il tipo I prevede una struttura in cui un operando un immediato e gli altri due sono registri: 6 + 2*5 = 16bit
i restanti 16bit sono utilizzati per limmediato. Uno scenario quello in cui degli operandi sono noti.
Essendo la macchina Load-Store non si pu prelevare un operando dalla memoria attraverso un operazione
aritmetico logica, quindi gli operandi o sono tutti nei registri o qualcuno un termine noto. Solo un
operando per deve essere un termine un noto(due operandi termini noti non ha senso). Deve esistere
quindi un solo immediato come sorgente non come destinazione(altrimenti si sta risolvendo una
equazione!). Il codice operativo della somma con immediato diverso dallistruzione somma senza
immediato. E il codice operativo che indica se listruzione di tipo R o di tipo I o J. Prelevata listruzione
questa va decodificata, capire di che tipo listruzione di cui ci si sta occupando, se di tipo R allora
gestisce i bit diversamente se listruzione di tipo I o J.

30

4. Le istruzioni del calcolatore MIPS


Tutte le istruzioni si differenziano per tipologia in una serie di 4 famiglie:

Trasferimento dati

Operazioni aritmetico logiche

Istruzioni di controllo (salto)

Istruzioni di calcolo in virgola mobile

Le operazioni di calcolo in virgola mobile sono distinte dalle operazioni logico aritmetico perch di fatto
interessano parti diverse del processore, perch sia i dati che lhardware per fare i calcoli sono diversi. Le
istruzioni logico aritmetico usano registri di tipo R e la parte del processore che esegue il calcolo lALU, le
istruzioni in floating point usano registri di tipo F e la parte del processore che effettua il calcolo non lALU
ma altre unit.
Pi che il codice operativo che un insieme di numeri, 1 e 0, utilizziamo una traduzione mnemonica,
insieme di lettere pi facili da ricordare.
4.1. Istruzioni di trasferimento dati
Nel trasferimento dati si intende il trasferimento dei dati dalla memoria ai registri e viceversa, oppure dai
registri ad altri registri.
Il modo per accedere a memoria attraverso luso dellimmediato da 16bit da sommare ad un registro
(istruzione di tipo I). Per accedere a memoria lindirizzo viene specificato sempre nella modalit in cui
listruzione di tipo I considera i 16bit di immediato da sommare a uno dei due registri.
La prima lettere delle istruzioni una L o una S: istruzioni Load o Store:
lettera
significato
L
Load
S
Store
L trasferimento dalla memoria al registro, S trasferimento dal registro alla memoria.
La seconda lettere precisa la dimensione del dato.
lettera
B
H
W
D

significato
Byte
Half Word
Word
Double Word

Ci sono alcune istruzione che hanno come ultima lettera una U (unsigned). La presenza o meno della lettera
U indica che il dato rappresentato con segno o senza segno(U). Se il dato in questione un byte e non
viene specificata la lettera U, il dato un byte signed altrimenti con la lettera U rappresenta un dato di un
31

byte unsigned. E importante specificare questo perch il registro di 32 bit, facendo la Load di un byte in
cui specifico un certo registro
LB R6 R7 100
Si ottiene lindirizzo di memoria sommando R7 a 100, e viene prelevato un byte dalla memoria puntata da
questo indirizzo e lo si memorizza in R6:
M[R7+100]->R6
Normalmente viene prelevata una parola, poich listruzione una LOAD di 1 byte(LB), ci si focalizza sul
byte di questa parola che viene messo in R6. Ora che in R6 ci sono gli 8 bit, i restanti bit verranno riempiti
con degli zeri se il numero insigne, altrimenti con il bit pi significativo, poich il numero sarebbe in
complemento a 2 in questo caso.
Se viene prelevato un dato in Double Word , 32bit, questo problema dellunsigned non esiste, visto che
questo comprende tutti i bit.
Questo problema dellespansione dei bit esiste solo nelle istruzioni LOAD, mentre nelle Store si andr a
scrivere in memoria solo il byte, i restanti bit non vengono toccati.
L.S, L.D, S.S, S.D sono istruzioni di LOAD e di STORE che fanno uso di registri floating point. Il punto, .
indica una LOAD o una STRE in un registro che contiene dati floating point, non sono registri R ma registri F.
La lettera S o una D indica la precisione, la dimensione, rispettivamente Single Precision(32bit) o Double
Precision (64 bit).
MFC0, MTC0 (Move From, Move To). La move from copia da un registro GPR(General Purpouse Register) a
un registro speciale (registri particolari per esempio il PC, uno dedicato allo stack pointer, dedicato a un
controllo di memoria ecc., cio registri che non vengono usati per immagazzinare i dati durante i calcoli
delle operazioni che fanno parte di un programma). Mentre listruzione Move To copia da un registro
speciale a un registro GPR.
MOV.S MOV.D Copiano dati in Singola precisione o Doppia precisione da un registro a un altro cio creano
un duplicato del dato.
A cosa potrebbe servire un duplicato del dato? Pu presentarsi il caso in cui il dato viene aggiornato in una
certa maniera per un ramo dellalgoritmo e in un'altra maniera in un'altra situazione dellalgoritmo. Con
un'unica operazione di tipo LOAD allinterno del processore, si accede una sola volta alla memoria e una
volta caricato, con loperazione MOV lo si replica.
MFC1, MTC1 Come MFC0, MTC0 fanno copia da registri speciali a registri GPR La move from copia da un
registro GPR(General Purpouse Register) a un registro speciale, solamente che queste usano dati di virgola
mobile. Quindi trasferiscono dati da registri in virgola mobile a registri in virgola fissa o viceversa. Bisogna
fare attenzione poich in questo passaggio di dati non avviene nessuna conversione! Non esistono
istruzioni per trasferire un numero in virgola mobile a registri speciali, per il semplice fatto che il contenuto
di questi ultimi , normalmente, ha un significato binario. Quindi, per esempio, dati che sono in registri
floating point non hanno niente a che vedere con un indirizzo. Per qualora fosse indispensabile trasferire il
contenuto in un registro floating point in un registro speciale, lo si fa utilizzando listruzione di tipo MTC1,

32

per copiare il contenuto dal registro floating-point in un registro intero GPR, e poi attraverso un istruzione
di trasferimento MTC0 si trasferisce il contenuto dal registro intero GPR a un registro speciale.

4.2. Istruzioni per operazioni logico-aritmetiche


DADD, DADDI, DADDU, DADDIU . Listruzione DADD effettua la somma. La prima lettera D indica che si
tratta di un dato DOUBLE WORD. DADD significa che loperazione una somma. Eventualmente ci sono
due altre lettere: I e U, o entrambe. La lettera U come abbiamo gi detto che il dato insigne, significa che
gli la somma avviene tra operandi unsigned, dati unsigned, la lettera I indica che la somma ha come
secondo operando un immediato invece che un registro.
Nellistruzione DADD sono specificati tre registri, due sorgenti e uno di destinazione, istruzione di tipo R.
Nellistruzione DADDI invece, sono specificati due registri, uno di destinazione e uno sorgente, e un
immediato considerato sempre come sorgente, istruzione di tipo I. Le istruzioni con la lettera I si chiamano
istruzioni di tipo immediato.
Come mai tutte queste istruzioni operano con dati di tipo Double? Quando si caricato il dato in un
registro, attraverso loperazione di LOAD, il dato che come abbiamo detto pu essere prelevato come byte,
half word, word o double word, nel momento in cui stato scritto nel registro stato espanso a 64bit,
stato tradotto in un double word. Ma ci si potrebbe chiedere: non si impiega meno tempo a sommare due
dati di tipo byte? Fare un operazioni con dati che sono byte comporta meno tempo rispetto a operazioni
con dati double, ma non servir risparmiare questo tempo perch non comporter dei vantaggi quando
penseremo alla gestione PIPELINE, la quale destiner a ogni fase dellistruzione, fetch decode execute, una
fetta di tempo. E se si destino a fare questa parte delloperazione, un tempo sufficiente a fare la somma di
un byte, ci si trova nei guai quando si dovr fare la somma di un double che richieder pi tempo. Ci
accorgeremo che non solo si dovr dimensionare al tempo di calcolo delloperazione pi lenta ma si dovr
fare in modo che le varie porzioni di tempo assegnate a ciascuna fase delloperazione dovranno essere
uguali. Per cui non si pu pensare di dare a questa parte di esecuzione un tempo diverso dallaltra parte
perch quando la prima istruzione star facendo unoperazione ci sar un'altra istruzione che star facendo
la decodifica, quindi queste due operazioni dovranno avvenire simultaneamente. Si capir meglio il
concetto quando parleremo della tecnica della pipeline.
Ci si pu ancora chiedere come mai durante la somma di due dati double , bisogna specificare signed o
insigned? Quando si effettuano delle operazioni logico-aritmetiche con dati insigned, in binario puro quindi,
queste possono generare un overflow. Il numero di bit necessari per esprimere il risultato delloperazione
maggiore dei bit a disposizione per contenerlo, ad esempio viene generato un bit al 65esimo posto, e il
risultato ottenuto nei 64 bit errato.
Supponiamo di avere un dato a 4 bit:
1 0 1 1 +
1 1 0 0 =
1|0 1 1 1

33

Il processore restituisce come risultato 0111 segnalando un overflow, ad indicare che il risultato ottenuto
errato.
Facendo un operazione con due numeri signed, quindi in c.a 2, la generazione di un overflow potrebbe
essere normale e non da considerare, perch quel bit, o quei bit perch potrebbero essere pi di uno, sono
il bit di segno che si propagano. Quindi il meccanismo di controllo alluscita dellALU, diverso se i dati sono
unsigned o signed, per questo motivo bisogna specificare se si sta operando con bit con o senza segno. Se
questi bit fossero stati signed, nellesempio precedente
1 0 1 1 +

(-5) +

Per leggere il numero in c.a 2, bisogna verificare

1 1 0 0 =

(-4) =

se il primo bit 1, segnare il meno, poi fare il

1|0 1 1 1

-9

c.a 1 e poi sommare 1.

Il bit che andato oltre un bit di segno, ad indicare che il numero non un numero positivo ma negativo.
DSUB, DSUBU Effettua la sottrazione. Non prevede limmediato perch fare una sottrazione con
immediato, equivale ad una somma con limmediato negativo. In questo modo non solo si risparmia un
codice operativo ma anche tutta lunit di controllo necessaria a controllare il processore nellesecuzione di
un istruzione, si risparmia sulla parte hardware.
DMUL, DMULU, DDIV,DDIVU Effettua una moltiplicazione o una divisione con dati signed o unsiged.
MADD ( o MACC in altri set, moltiplicazione ad accumulazione) E un operazione che nella stessa istruzione
effettua una moltiplicazione e una somma. C un piccolo problema nella MADD. Si prevedono due
operandi per fare il prodotto, uno per fare la somma e infine ci vuole un quarto operando per scrivere il
risultato. Ma le istruzioni prevedono al massimo tre operandi. Quindi per fare la MADD , un operando ha
doppia valenza , sia come risultato che come operando. Quindi chi riceve il dato per fare la somma quello
che poi riceve anche il contributo del risultato.
Essa fa una cosa del tipo:
X += Y * Z
che sarebbe
X= X + (Y*Z)
Dove X, Y, Z sono i tre registri specificato nellistruzione. Questa operazione MADD molto frequente: c
un valore che si accresce dei prodotti di altri due fattori, come nel prodotto scalare tra due vettori:

Questo pu essere fatto, con un loop di istruzioni di tipo MADD.

34

AND, ANDI Operazione di AND logico sia fra due registri che fra un registro e un immediato. Se di una AND
uno dei due operandi noto al compilatore, non ha senso sprecare un registro per metterci questo
operando. Loperando viene inserito nellistruzione stessa come immediato. Questo operando immediato
non potr essere un numero che richieder pi di 16 bit, perch le operazioni con immediato hanno un
immediato di 16 bit. Ad esempio, non possibile eseguire un operazione di AND con un dato da 32 o 64 bit
e un immediato da 50 bit. Bisogna in qualche modo scrivere questo immediato in un registro e poi fare
lAND fra i due registri.
Gli AND con immediato sono utile per fare le classiche operazioni di MASCHERAMENTO. Di un dato a 64
bit, per esempio, si ha la necessit di vedere, analizzare solo alcuni bit. Per esempio se si vuole vedere se
un numero pari o dispari: di questi 64 bit interessa guardare il bit meno significativo. Banalmente, si
utilizza una maschera: un immediato composto da tutti 0 e il bit meno significativo invece vale 1, e si
effettua loperazione di AND tra il registro che interessa analizzare e questo immediato.
0
0
0
0
0
0

0
1
Esempio di maschera, immediato da 16 bit con tutti i bit
settati a zero eccetto il bit meno significativo.

Loperazione di AND tra il registro e questo immediato risulter vero o un falso e quindi un valore 1 o un
valore 0 a seconda se il numero rispettivamente dispari o pari.
OR, ORI, XOR, XORI Mentre con lAND abbiamo visto che si fanno le operazioni di mascheramento, con lOR
si fa un operazione di imposizione di sovrascrittura. Se si vuole forzare una certa parola a contenere per
esempio nel 7 bit il valore 1, si effettua lOR di quella parola con limmediato composto da tutti 0 e al
settimo bit il valore 1.
LUI( Load Upper Immediate). Durante loperazione di load di una word, si copia il contenuto di memoria nei
32 bit meno significativi del registro da 64 bit e i restanti vengono espansi in segno. Con listruzione LUI c
la possibilit di caricare un immediato, da 16 bit, scritto nellistruzione stessa, nella parte superiore del
registro, nei bit del registro che vanno da 32 a 47. Limmediato viene espanso in segno, in modo che da
occupare con la replica del bit di segno i restanti bit da 48 a 63, mentre i bit meno significativi quelli che
vanno da 0 a 30 sono posti a zero.
63 62 48 47 46 32 31 30
1 1
1 1 0
1 0 0 0
espansione in segno

1 0
0 0 0

immediato

DSLL, DSRL, DSRA,DSLLV( Double Shift Left, Double Shift Right) fa scorrere il registro verso sinistra o verso
destra. La lettera A o L indica lo shift Arithmetic o Logic.
Lo shift logico verso destra o verso sinistra consiste rispettivamente nel moltiplicare o dividere per potenze
di due a seconda del numero di posizioni di cui si sta shiftando, facendo entrare quindi degli zeri da sinistra
o da destra.
Lo shift aritmetico verso sinistra si comporta come lo shift logico verso sinistra, vengono fatti entrare degli
zeri da destra. Mentre nello shift aritmetico verso destra viene fatto entrare da sinistra il bit pi
significativo, il bit di segno. Quindi c una differenza tra i due shift solo durante lo shift verso destra. Per

35

questo motivo Shift Left ha solo DSLL e non DSLA, perch non ha senso distinguere fra shift a sinistra logic e
arithmetic. La differenza fra Logic e Arithmetic appare solo in caso di shift right.
Queste operazioni di Shift avvengono con un operando immediato. Esistono operazioni di shift con
operandi che sono registri, DSLLV. La lettera V indica variabile e cio che il numero delle posizioni da
scorrere in un registro.
SLT, SLTI, SLTU, SLTIU (Set Less Than) Imposta uguale a 1 un particolare registro di condizione se un registro
minore di un altro registro. Ad esempio:
SLT

R1

R2

R3

R3 viene settato a 1 se R1 minore di R2


SLTI R1

R2

Se R1 minore dellimmediato , R2 viene settato uguale a 1. La eventuale U di unsigned fa il confronto


tenendo presente che i dati scritti sia nellimmediato che nel registro sono dati unsigned.

4.3. Istruzioni di controllo


Come abbiamo gi detto ci sono due gruppi di istruzioni di controllo:
-

Quelle che saltano in maniera CONDIZIONATA

Quelle che saltano in maniera INCONDIZIONATA

Ogni produttore ha un modo diverso di chiamare queste istruzioni, per una letteratura quella di
classificare come BRANCH le istruzioni di salto condizionato e JUMP le istruzioni di salto incondizionato. E
opportuno considerare delle condizioni abbastanza semplici da valutare, per cui le condizioni in caso di
branch saranno limitate. I test che si fanno per verificare se saltare o meno sono quelli relativi
alluguaglianza o alla diversit. Quindi non si potr avere un branch se R1 >0 o se R1 > R2; si potr avere un
salto soltanto se R1== R2 o se R1!=R2 .
Perch non si possono fare istruzioni di branch con immediato? Listruzione di tipo branch, di tipo
immediato: cio codificher due registri e limmediato il quale sar necessario per indicare dove saltare.
Poich limmediato serve a codificare dove saltare non pu essere utilizzato per verificare una condizione.
BEQ, BNE (Branch Equal) Confronta due registri se sono uguali o meno ed effettua il salto.
BEQZ, BNEZe effettua il contronto tra un registro e un immediato, limmediato 0. Non c la possibilit di
scegliere un altro immediato. Zero non va scritto nel campo immediato perch lunico immediato
consentito. Siccome il pi delle volte quando ci sono da fare salti, il confronto con limmediato 0, allora si
introdotto un codice operativo per fare il salto relativamente allimmediato 0. Nellimmediato bisogna
inserire il numero che sommato allattuale valore di PC che stato gi aggiornato con +4,, produrr
lindirizzo dove saltare. Ricordiamoci che limmediato del branch, non lindirizzo assoluto: un valore che
va sommato al PC pere ottenere lindirizzo dove saltare, tenendo conto che il PC gi stato incrementato di
4. Quindi per saltare di due istruzioni pi avanti dovr sommare il numero 4 invece che il numero 8.
36

Salta se R1=R2?
BEQ

R1 R2 indirizzo

Salta se R1!=R2?
BNE

R1 R2 indirizzo

Salta se R1>R2?
SLT R3 R1 R2

se R1>R2 allora R3 = 1 altrimenti R3 =0

BNEZ R3 indirizzo
BC1T, BC1F le istruzioni di salto che fanno il controllo sul bit di confronto relativamente alle operazioni
floating point
MOVN, MOVZ Copiano i dati da un registro allaltro, sempre di tipo R, qualora loperando di test
negativo o zero.
Copia R1 in R2 se R3 uguale a zero
MOVZ R1

R2

R3

Copia R1 in R2 se R3 minore di zero


MOVZ R1

R2

R3

Le istruzioni di salto incondizionato:


J, JR (jump e jump con registro) Jump un istruzione con immediato a 26 bit, mentre jump con registro
un istruzione di tipo R dove il registro contiene lindirizzo dove saltare. Mentre i 26 bit di immediato si
sommano al PC, Jump di tipo R (JR) specifica un registro che specifica lindirizzo dove saltare.
JAL, JALR oltre a fare esattamente cosa fa la Jump si preoccupano di salvare, prima di saltare, il contenuto
del PC. Queste istruzioni prendono il contenuto del PC, quando questo e gia stato incrementato , e lo
salvano nel registro R31. Questo un meccanismo per gestire il ritorno da una routine. Per attenzione il
PC viene memorizzato in un registro non in uno stack, di conseguenza se nella procedura avviene una
chiamata a unaltra routine viene perso il valore del PC e sostituito con quello nuovo.
TRAP Istruzioni particolari che servono a trasferire il controllo a procedure del sistema operativo. Succede
che durante lesecuzione del programma(applicazione utente) , il programma stesso preveda che in un
certo momento occorre trasferire il controllo a una particolare procedura del sistema operativo.
ERET (Exception Return) Ci sono una serie di casi in cui il programma viene interrotto. Fondamentalmente
queste casistiche vengono raggruppate nelle:
1) ECCEZIONI sono situazioni che si verificano in maniera SINCRONA, cio quel programma, tutte le
volte che verr eseguito con quei dati generer sempre quelle eccezioni. Esempio: la divisione di un
numero per zero.
37

2) INTERRUPT o INTERRUZIONI sono come le eccezioni ma si rivelano in maniera imprevista, perch


non dipendono dalla struttura e dalla natura tipica del problema e dei suoi dati ma dipendono
anche da fenomeni esterni allesecuzione stessa del programma.Esempio: diamo il comando di
stampa alla stampante e viene a mancare la carta, o quando un sistema operativo non trova il file.
Questa istruzione consente di tornare nel punto in cui si era prima di essere andati ad eseguire leccezione.

38

Esercizi
Esercizio n.1:
Supponendo che all'indirizzo K ci sia la prima istruzione del programma, saltare all'indirizzo K+1000 nel caso
in cui R2 sia maggiore o uguale a R3.

Svolgimento:
1. K:
2. K+4:

SLT R1, R2, R3


BEQZ R1, 248

Commento: La prima riga, serve a confrontare i due registri, e setta R1=0 se R2>=R3, la seconda riga, fa un
branch nel caso in cui R1=0.
ATTENZIONE: 248, indica lo scostamento del program counter(ricorda che il PC non pu essere modificato
direttamente con il registro, pu essere solo incrementato), lo scostamento per va considerato tenendo a
mente che ogni istruzione scosta il PC di 4. Quindi, quel 248 deriva dal fatto che ci troviamo all'istruzione 8.
1000-8=992 992/4=248. Questo, solo nel caso in cui io considero lo spiazzamento a MENO DEI DUE BIT
MENO SIGNIFICATIVI. Se usassi una notazione normale, dovrei mettere 992.

Esercizio n.2:
Scrivere una chiamata ad una subroutine supponendo che l'indirizzo della subroutine sia a 10000. Scrivere
la subroutine in modo che faccia questo: Copia il valore presente in M[100] e lo scrive in M[200].

Svolgimento:

1. DADDI
2. JALR

R5, R0, 10000


R5

Subroutine:
1. LD
2. SD
3. JR

R1, 100(R0)
R1, 200(R0)
R31

Commento: Innanzitutto, ERET non un'istruzione valida in questo caso perch fa riferimento
esclusivamente a routine del sistema operativo. La prima riga, serve a scrivere in R5 l'indirizzo
10000(sarebbe come scrivere in R5 la somma tra R0, che si trova a 0, e l'immediato 10000). La seconda riga,
permette di fare un salto incondizionato all'indirizzo R5 che grazie alla prima riga 10000 e memorizza in
R31 il valore dellistruzione che si trova subito dopo JALR. Una volta alla subroutine il programma fa prima
una load, poi una store, e infine con JR R31, ritorna all'istruzione immediatamente successiva alla JALR.
Ricorda che il registro dell'istruzione successiva alla JALR viene messo di default in R31. (R0) serve a
39

specificare l'indirizzo in cui fare la load/store. Sarebbe come scrivere: Fai la Load dallindirizzo di memoria
100(dove 100 indica lo scostamento da R0, che di default uguale a 0) e fai la Store dello stato dato R1 a
200(dove anche in questo caso 200 indica lo scostamento da R0).

Esercizio n.3:
Fare la divisione tra R1 ed R2 in modo che se R2=0, il programma lasci la gestione dell'eccezione al sistema
operativo.
Svolgimento:

1.
2.
3.
4.

BEQZ
DDIV
J
TRAP

R2, 2
R3, R2, R1
1

40

CAPITOLO 6

ARCHITETTURA CALCOLATORE PER LE OPERAZIONI CON DATI


INTERI
1. Fasi Di Una Istruzione
Le fasi dell'istruzione sono delle operazioni che vengono svolte durante l'esecuzione di ogni istruzione.
Un'istruzione per essere eseguita deve innanzitutto essere letta dalla memoria e caricata nel processore.
Questa prima operazione viene chiamata Fetch(prelievo), oppure Instruction Fetch (IF).
Una volta prelevata occorre che venga decoficare, questa seconda fase prende il nome di Instrucion Decode
(ID). Terminata la decodifica dell'istruzione il processore in grado di capire cosa fare, a questo punto entra
in attivit la fase di Execute(esecuzione). Questo nome non deve trarre in inganno, per esecuzione si
intende tutto il complesso delle fasi dell'istruzione; mentre per FASE di Execute, si intende la fase che
impegna l'ALU del processore se operiamo con dati interi, oppure che impegna l'unit di calcolo Floating
Point, se stiamo operando con numeri a virgola mobile.
Quando listruzione ha bisogno di accedere alla memoria, si avr la Fase di accesso a memoria che prende il
nome di MEM(sia scrittura che lettura dalla memoria). Ricordiamo per che la stessa istruzione non pu sia
leggere che scrivere in memoria, deve fare solo una delle due.
Infine cla fase di scrittura in un registro, che prende il nome di Write Back (WB).
Dall'execute in poi, sono fasi che esistono o meno a seconda dell'istruzione. Tutte le istruzioni vengono
eseguite in maniera identica per quanto riguarda la fase di Fetch e di Decode.
Nella fase di decodifica, mentre l'istruzione si trova nell'Instruction Register (IR), il processore svolge anche
un'altra mansione, per accelerare i tempi, si porta avanti con il lavoro e effettua la Precarica degli
operandi, o meglio prepara i registri che dovrebbero essere usati per fare i calcoli della fase di Execute.
Attenzione la ALU la usiamo anche se l'istruzione di tipo I(con immediato), o anche durante una LOAD ecc.
praticamente la ALU si usa quasi sempre.
Supponiamo ad esempio di voler eseguire un'operazione che richiede l'uso dei registri R1 ed R2, mentre
viene effettuata la decodifica, si porta in ingresso all'ALU gi R1 ed R2 e anche limmediato esteso in segno,
poi una volta terminata la decodifica vengono utilizzati i dati che realmente servono nell'ALU e viene
eseguita l'operazione decodificata. Tutto questo serve a velocizzare i tempi di esecuzione perch ancora
prima che la decodifica termini, si hanno gi tutti i dati pronti per entrare nell'ALU e quindi si riuscir ad
accederci in maniera pi veloce, infatti non si avr bisogno di caricarli dopo la decodifica. Questa operazione
di precarica potrebbe anche essere stata un'operazione inutile, come nel caso di una Jump poich non si
utilizzano i registri, ma non un problema, l'importante che questo tipo di operazione non faccia danni.
Questa azione viene fatta in maniera speculativa, o meglio, nel caso in cui l'istruzione richiedesse entrambi i
registri, allora si risparmiato tempo; mentre nel caso in cui non si risparmiato tempo, comunque non ci
sono stati danni.
Per esempio, invece di caricare i registri, potrei pensare di preparare un immediato; per preparare un
41

immediato si intende prendere i 16 bit di quest'ultimo ed espanderli in segno.


Durante l'esecuzione non va tralasciato il PC, perch contenendo l'indirizzo dell'istruzione che devo
eseguire, alla fine dell'istruzione va sempre incrementato, a prescindere dall'istruzione. L'incremento pu
essere fatto in qualsiasi momento dell'esecuzione dell'istruzione, purch avvenga sempre nell'ambito
dell'istruzione stessa.
Per preferibile anticipare l'incremento del PC perch possibile eseguire l'istruzione successiva, prima
che l'istruzione corrente sia finita(concetto di pipeline). Quindi, poich la prima fase dell'istruzione la
Fetch, necessario incrementare subito il PC perch ci permette di anticipare l'esecuzione dell'istruzione
successiva.
L'incremento del PC quindi pu essere fatto durante la fase di Fetch, e non necessita di alcun calcolo
durante la fase di Execute. In conclusione si pu pensare di : leggere il PC , caricare listruzione relativa al PC,
incrementare il PC, PC+4, perch ora il suo valore non serve, e proseguire gi con la fetch di un'altra
istruzione relativa al nuovo PC, mentre laltra istruzione prosegue con le altre fasi.

2. Architettura Calcolatore Per La Gestione Di Dati Interi


Questo lo schema dellarchitettura del processore che opera sui dati ad aritmetica intera. Sono
rappresentati gli elementi, i moduli della parte del processore che opera su dati ad aritmetica intera.

Analizzando la fase di Fetch: cominciando da sinistra (1) il primo modulo il Program Counter che
rappresenta un registro in cui sono scritti dei bit che indirizzano la memoria relativamente al prelievo
dellistruzione da eseguire (fase di fetch). Il PC va quindi in ingresso alla Memoria delle Istruzioni(2).
La memoria in generale divisa in due moduli distinti identici; lunica diversit che sono due aree una
destinata a contenere le istruzioni dei programmi(Memoria delle Istruzioni (2)) e unaltra che serve a
contenere i dati(Memoria dei Dati (11)). Questa architettura della memoria fa comodo perch quando si
gestisce lesecuzione di una istruzione in simultanea a un'altra istruzione questo risolve il problema causato
42

dai conflitti strutturali. Nella tecnica della pipeline, che analizzeremo successivamente, quando viene
eseguita una istruzione, questa non viene eseguita da sola ma nel frattempo viene eseguita anche un'altra.
Bisogna garantire per permettere questo alcune condizioni: risolvere i conflitti strutturali e i conflitti di
dato. I conflitti strutturali sono i conflitti che insorgono tra due istruzioni che vogliono usare la stessa
risorsa. Se ci sono due istruzioni che hanno bisogno di usare lALU, queste non possono essere eseguite
contemporaneamente. Un conflitto strutturale che pu avvenire utilizzando un'unica memoria che
contiene istruzioni e dati, nasce quando ad esempio ci sono due istruzioni che accedono a memoria, ad
esempio avviene la fetch di una istruzione e nel frattempo viene eseguita una load da un'altra istruzione.
Durante la fase di accesso a memoria di una Load per esempio che vuole caricare un dato(accesso a
memoria) non pu avvenire contemporaneamente il prelievo di un'altra istruzione. La memoria o pensa a
fare la load oppure pensa a fare il prelievo di una istruzione(conflitto strutturale). Per risolvere questo
conflitto la cosa pi semplice di implementare larchitettura Harvard in cui si tengono distinte le due
memorie, un area in cui ci sono i dati da leggere e scrivere, e laltra area che contiene le istruzioni di un
programma, che il Loader, la parte del Sistema Operativo che si occupa di caricare in memoria il
programma da eseguire, memorizza. Un sistema multi task consente lesecuzione sul processore di pi
processi, cio vengono gestiti simultaneamente pi processi. Questo vuol dire che il processore esegue in
un istante una istruzione alla volta. Ci sono poi varie politiche della gestione della CPU per esempio in base
alla priorit dei processi o alla suddivisione dei tempi. Oltre alla cpu bisogna gestire per anche la memoria.
In una zona della memoria ci sono i dati di un processo o di un altro, in fase di compilazione non si conosce
per esempio se lindirizzo 1000 disponibile a memorizzare una variabile. Ci sono tecniche per gestire
questi indirizzi attraverso la rilocabilit di questo indirizzo. Lindirizzo viene gestito in modo da non essere
un indirizzo assoluto ma relativo a un area di memoria allocata per quel processo, in modo da non
preoccuparsi se allindirizzo 1000 c un altro processo, ma quellindirizzo 1000 relativo allarea di
memoria dedicata a quel processo. Se il sistema anche multiutente in contemporanea quel processore sta
lavorando per diversi utenti eventualmente ciascun utente ha in attivo diversi processi, e la divisone delle
risorse ancora pi delicata.
Per risolvere dunque i conflitti strutturali, dato che ci sono pi istruzioni da eseguire in simultanea, la
memoria divisa in due diverse aree indipendenti, Memoria delle Istruzioni e Memoria dei
dati(Architettura Harvard) in maniera tale che durante la fetch(memoria delle istruzioni) di una istruzione,
un'altra istruzione pu effettuare una load o una store(memoria dei dati). In questo modo si evita il
conflitto strutturato per laccesso a memoria.

La memoria delle istruzioni riceve lindirizzo dal PC e tira fuori


listruzione che viene memorizzata nel registro IR Instruction
Register(3). NellIR c listruzione da eseguire che deve essere
decodificata per essere eseguita. Una parte del processore(che non
disegnata) lUnita di Controllo CU (Control Unit), si preoccupa di capire
che tipo di istruzione stata prelevata e qual il compito di questa
istruzione, cosa questa istruzione deve fare: fase di decodifica.

43

Durante questa fase di decodifica il processore si porta avanti nel lavoro: fa una precarica degli operandi.
Facendo delle ipotesi, se questa istruzione di tipo R allora
questi gruppi di bit identificano i registri, due di input e uno
di output (4), quelli di input vengono preparati in ingresso
allALU nei campi A e B(7). Nello stesso tempo se invece
listruzione di tipo I un gruppo di bit identificano i registri e
i 16bit invece un immediato che va espanso in segno (5) e
questi vengono mandati sempre in ingresso allALU nei
campi A B e Imm; mentre ancora se listruzione di tipo J i
26bit espansi in segno (6) codificano un immediato che va
messo sempre in ingresso allALU. Quindi in parallelo
durante la decodifica dellistruzione vengono preparati una
serie di operandi. In particolare i registri vengono copiati nei
campi A e B, e gli immediati invece nel campo Imm e questi
vengono mandati in ingresso allALU (7). Sono quindi questi
che vengono poi effettivamente messi in ingresso allALU.
Questa operazione avviene a prescindere dal risultato della
decodifica, cio si preparano tutti questi operandi senza conoscere effettivamente il risultato della
decodifica(se effettivamente listruzione di tipo R o I o J). Pu sembrare lavoro inutile ma non lo !
Terminata la decodifica sono gi tutti pronti i dati per andare in ingresso allALU, per solo alcuni di questi
operandi andranno effettivamente in ingresso allALU. A seconda del tipo di istruzione che deve essere
eseguita andranno in ingresso determinati operandi. Infatti In corrispondenza degli ingressi dellALU ci sono
dei Multiplexer (Mux) (8) che fanno passare in ingresso un solo operando tra i tanti che si presentano.
I multiplexer sono circuiti elettronici digitali che dati n ingressi, in uscita ne indirizzano uno solo di questi
ingressi a seconda del valore di un ulteriore ingresso chiamato Selettore (SEL). Esempio di un multiplexer a
due ingressi(I0 e I1):

Se il selettore vale 0,SEL = 0, luscita I0 altrimenti se


il selettore vale 1, SEL=1, luscita I1.
Questi dispositivi introducono un cammino critico, il
percorso con la latenza pi lunga. Luscita dal
multiplexer viene letta dopo un certo tempo che sia
tale da essere appena superiore al tempo necessario
affinch il segnale dallorigine si propagato fino alla
fine, velocit delle piste su cui scorrono i bit pi il
tempo dei moduli che hanno ritardi standard. La
porta Or ad esempio ha una latenza di tot
microsecondi, la porta And avr un'altra latenza quella con linverter avr un'altra latenza ancora. La
latenza totale sar data dalla latenza dellOR pi il massimo (max) tra le latenze delle porte AND, che
potrebbero avere la stessa latenza. Attenzione: le due porte AND sono in parallelo quindi la latenza
complessiva di queste non la somma tra le latenze ma il max tra le due.
44

2.1. Istruzione Di Tipo R


Come abbiamo gi detto la fase di Fetch e quella di Decode sono uguali per ogni istruzione. Le fasi
successive invece si differenziano a seconda del tipo di istruzione, I, R o J e alle volte anche dallistruzione
stessa come nel caso di Load e Store oppure istruzioni aritmetico logiche o salti.

Ora ipotizziamo, finita la decodifica, che listruzione di tipo R


ed una istruzione di tipo aritmetico logico. Si predispone
lALU a fare loperazione tra i due ingressi, lALU si configura a
fare loperazione , ad esempio AND tra i due ingressi. Questi
ingressi non vengono presi dal banco dei registri, poich i
registri operandi dellistruzione sono stati trascritti in A e B.
Quindi in ingresso allALU vanno A e B.
Ritornando allingresso degli operandi nel multiplexer, essendo
listruzione aritmetico logica di tipo R(per ipotesi) in ingresso
allALU devono andare A e B. Alla prima porta andr il latch
A(multiplexer in alto, lasciando stare per adesso laltro
ingresso NPC) e in ingresso alla seconda porta dellALU il
multiplexer, con il selettore impostato in una certa maniera
dallunit di controllo, far passare il latch B. Lunit di
controllo, visto che listruzione di tipo R, setta il selettore del
primo multiplexer a 1 per esempio per far passare A, mentre
setta il selettore del secondo multiplexer a 0 per far passare B.
Una volta che gli operandi sono entrati nellALU questa eseguir loperazione che lUnit di Controllo
imporr di fare in base al codice operativo, per esempio AND.
2.2. Istruzione di tipo I
Ora se invece listruzione aritmetico logica di tipo I, gli operandi sono quindi un registro e un immediato di
16 bit. Ora A conterr il valore del registro, in B ci sar sempre qualcosa, solo che ora mentre il primo
multiplexer far passare comunque A, il secondo multiplexer non far passare B in ingresso allALU ma far
passare limmediato Imm. LUnit di Controllo imporr quindi loperazione nellALU fra il registro e
limmediato.
Ora il risultato prodotto dallALU(a seconda se loperazione di tipo R o I o J) viene memorizzato in un
latch: ALU output (9).
Il LATCH un circuito o elemento di circuito impiegato per mantenere un particolare stato, per esempio,
acceso o spento oppure vero logico o falso logico. Un latch cambia stato solo in risposta a un particolare
input.
Da ALU output questo partono tre diramazioni, che verranno percorse in base al tipo di operazione:
Aritmetico-Logica, Accesso a memoria (Load-Store), Salto.

45

Poich stiamo considerando loperazione di tipo aritmetico


logico, lUnit di Controllo attiver il percorso pi in basso,
quello che porta allultimo multiplexer(10), in realt quelle tre
linee sono tutte attive per le altre due linee termineranno li e
non andranno avanti. Quella che termina nel multiplexer (10)
sar propagata in uscita da quel multiplexer, lUnit di Controllo
setta il selettore di questo multiplexer in modo tale da far
passare appunto il valore di ALU output, che ritorna indietro al
banco dei registri e si andr a scrivere nel registro destinazione, quello che scopriamo essere codificato in
quei 5 bit che sono la terza freccia a partire dallalto (registro di destinazione codificato nellistruzione).
Questo processo di scrittura del risultato delloperazione in un registro si chiama WRITE BACK.
Vediamo cosa succede se invece loperazione non logico aritmetica ma una LOAD. Una LOAD contiene
due registri e un immediato, istruzione di tipo I. Un registro per indicare dove andare a scrivere il dato letto
dalla memoria e laltro registro che invece viene sommato allimmediato per ottenere lindirizzo della
memoria.
Con una LOAD ci che avviene fino alla decodifica lo stesso come per un operazione di tipo logico
aritmetico. La situazione questa: in ingresso allALU ci sono A e B che contengono il contenuto dei due
registri. In realt A contiene il registro per lo spiazzamento da sommare allimmediato mentre B non
dovrebbe contenere nessun dato utile, e poi c limmediato da 16 bit espanso in segno (5). Una volta
decodificata listruzione LOAD, lALU non far la somma tra A e B, ma tra A e limmediato che dar come
risultato questa volta un indirizzo di memoria. Lunit di controllo quindi setter il selettore del secondo
multiplexer in maniera tale da far passare in ingresso allALU Imm e non B. Dal punto di vista dellALU la
somma in questo caso equivalente alla somma con un istruzione di tipo ADD. Il risultato viene poi
memorizzato sempre in ALU output (9), ma il percorso che verr effettuato tra le tre diramazioni non pi
quello seguito dalloperazione logico aritmetica cio verso lultimo multiplexer(10) per andare in write back,
ma sar quello che va nella Memoria dei Dati (11).
La Memoria dei Dati costituita da un ingresso(monodirezionale) che contiene un indirizzo, un ingresso
per la scrittura di un dato in memoria, un uscita per il dato che viene letto dalla memoria e un ingresso di
controllo che dice alla memoria se loperazione da fare di scrittura o di lettura. Facciamo un esempio:
Lettura(ingresso di controllo): viene letto lindirizzo(ingresso indirizzo), viene preso quello che c in
quellindirizzo e viene messo in uscita(uscita dati); Scrittura(ingresso di controllo): viene letto
lindirizzo(ingresso indirizzo), viene preso il dato in ingresso(ingresso dati) e viene scritto in quellindirizzo.
LUnit di Controllo avvisa, nel caso in esame(istruzione LOAD), la
Memoria dei Dati che loperazione da fare un operazione di
lettura. Dalla Memoria dei Dati quindi uscir il dato che viene
memorizzato adesso in un latch LMD(Load Memory Data) (12).
Nella gran parte dei processori esiste anche MDR(Memory Data
Register) che lavora per le operazioni di tipo STORE. Nel processore
in esame invece esiste solo LMD che lavora per le LOAD, perch le
STORE vedremo pi avanti vengono gestite diversamente.

46

Ora questo dato, memorizzato in LMD, deve essere scritto nel registro(registro di destinazione specificato
nella istruzione). LMD subisce anchessa una write back: il mux (10) far passare in retroazione LMD invece
che ALU output (lUnit di controllo setter il selettore di questo mux in maniera tale da far passare questa
volta LMD). Quello che sta in ALU output conterr sempre qualcosa, quando viene scritto qualcosa in un
latch questo ci resta sempre fino a quando non viene scritto qualcosaltro sopra. Per cui quel mux(10)
mander LMD nel banco dei registri e memorizzo questo nel registro destinazione (WB).
Vediamo cosa succede nel caso di una istruzione di tipo STORE. Ci che avviene in una STORE abbastanza
simile alla LOAD, cambia solo luso della memoria: questa volta la memoria viene utilizzata per scriverci
dentro. Nelloperazione STORE come la LOAD ci sono due registri e un immediato. Ora il campo A contiene
il registro da sommare allimmediato per ottenere lindirizzo di memoria in cui scrivere un dato. B questa
volta indica il registro da scrivere in memoria, contiene il dato da scrivere in memoria. Infine c
naturalmente limmediato da 16bit. Terminata la decodifica dellistruzione e quindi una volta capito che
listruzione da fare una STORE, serve calcolare lindirizzo tra A e limmediato. Lunit di controllo quindi
far passare dal primo mux A e dal secondo Imm invece che B. LALU effettua la somma e salva il risultato in
ALU output. LALU effettua la somma come una semplice operazione di somma, non sa che il risultato
questa volta un indirizzo; in realt lALU non sa neanche che
in ingresso questa volta c un registro e un immediato,
questa fa la semplice somma degli operandi al suo ingresso
che gli arrivano. Questo risultato ora un indirizzo che lunit
di controllo invia in ingresso alla memoria dati per effettuare
loperazione di scrittura in quellindirizzo. Lunit di controllo
avvisa inoltre la memoria che loperazione da fare ora una
operazione di STORE, quindi di scrittura in memoria, e quindi
scrive lingresso dato che corrisponde a B nellindirizzo
inviato. Il dato prelevato da B viene mandato direttamente in ingresso alla memoria dati (13). In LMD
questa volta c scritto il valore precedente, il vecchio valore(non viene cancellato niente, quello che cera
prima c anche adesso). Loperazione di STORE finisce qui, nella scrittura del dato nella memoria dati.
Ricordiamo che in tutte queste istruzioni avviene anche lincremento del Program Counter: prendere il PC
sommarli 4 e scrivere questo risultato nel PC. Normalmente questa operazione pu essere effettuata
dallALU, sommare 4 al PC: dopo aver mandato il PC in ingresso alla memoria istruzioni, si invia al primo
ingresso dellALU mentre nel secondo ingresso dellALU si mette 4 , e avvisata lALU di effettuare una
somma, scrivere poi questo risultato nel PC. Questa procedura va bene se le istruzioni vengono eseguite
una alla volta, per poich come abbiamo gi detto vengono eseguite pi istruzioni contemporaneamente,
con questo metodo vengono a crearsi nuovamente conflitti strutturali, come nel caso della memoria. Se si
utilizza lALU per fare incrementare il PC e si usa ancora lALU per calcolare un istruzione di somma (ADD)
per esempio, si crea nuovamente un conflitto. Come
precedentemente abbiamo separato la memoria in due aree di
memoria(istruzioni e dati) cos ora dovremmo replicare lALU! Ma
replicare lALU per incrementare il PC non ha molto senso, poich
loperazione da fare solo la somma con 4, e quindi in sostanza
conviene fare un sommatore dedicato (14), che ancora pi
semplice del normale sommatore, poich fa solo la somma +4.
Questo sommatore ha ai suoi ingressi sempre il PC e 4.

47

Il PC quindi va in ingresso sia alla memoria delle istruzioni e sia al sommatore. Il risultato di questo
sommatore viene memorizzato in un latch: NPC (New PC) (15). New PC va sia in ingresso al mux davanti
allingresso dellALU (16) e sia in ingresso ad un mux (17) che ha ai suoi ingressi il dato preso da ALU
output(una delle tre diramazioni viste precedentemente) e appunto NPC. Luscita di questo mux va a
terminare nel PC che viene quindi aggiornato.
Se listruzione non di salto, cio un istruzione di tipo aritmetico logico oppure LOAD o STORE, mentre
vengono eseguiti i procedimenti descritti prima, si esegue anche la somma di PC +4 in NPC, il quale va in
ingresso al mux (17). Lunit di controllo setter questo mux in maniera tale che alluscita presenti NPC che
in andr poi in PC (1).
Cosa succede se listruzione di tipo JUMP, che consiste ora nel prendere limmediato da 26 bit questa
volta, e di sommarlo al PC+4 per ottenere cos il nuovo PC.
Questa rappresentazione della gestione dellimmediato da 26 bit non fedele, nel seguito vedremo come
avviene realmente questoperazione, perch sar un operazione da fare nel minor tempo possibile. Con la
rappresentazione descritta conosceremo lindirizzo dove saltare solo dopo un po di tempo, questo crea
problemi poich che istruzioni vengono eseguite in simultanea. Per adesso limitiamoci quindi a
schematizzare e a gestire in questo modo questo tipo di operazione con immediati da 26bit(il modulo per
lestensione in segno dellimmediato da 26bit stato aggiunto dagli autori di questo manuale con il
consiglio del professore Francesco Marino).
Con questa rappresentazione quindi durante la fase di fetch della JUMP, viene calcolato PC+4 in NPC.
Terminata la decodifica dellistruzione (JUMP) lunit di controllo dal primo mux in alto far passare in
ingresso allALU NPC (16) che ricordiamo vale PC+4, mentre dal secondo mux far passare limmediato da
26bit espanso in segno (6). Il salto avviene rispetto a PC+4, come abbiamo gi detto.In uscita allALU viene
calcolato un indirizzo di salto(non un indirizzo di memoria).
Delle tre diramazioni che partono dallALU output lunit di
controllo abiliter solo quella verso lalto, verso il mux (17),
mentre dir alla memoria dei dati tu dormi! E al mux (10) tu
fottitene(in americano per!).
Il mux(17) questa volta non far passare pi NPC, ma ALU
output che si andr a scrivere in PC: stato effettuato il salto!
Vediamo cosa succede con operazioni di tipo BRANCH(salti
condizionati). Il meccanismo simile al JUMP, presenta solo
alcune diversit. Lindirizzo dove saltare viene calcolato
sommando al PC+4 un immediato che di 16bit (5) e non da 26.
Inoltre il salto viene effettuato solo se viene verificata la
condizione. Questa condizione un test rispetto a zero di un
registro specificato nellistruzione stessa (BEQZ per esempio).
Dei dati A, B(che non verr utilizzato) e limmediato posti in
ingresso, A rappresenta il dato su cui andare a fare il test, mentre limmediato il valore che andr
sommato a NPC. Mentre nella JUMP A non veniva considerato(passava NPC in ingresso allALU), ora A viene
inviato nel modulo di test Zero (18), che effettua un test di A rispetto a zero. Il risultato del test andr ad
impostare il registro Cond (19) che conterr quindi il valore Vero(saltare) o Falso(non saltare), a seconda se
48

stata verificata o meno la condizione.


Questo registro andr a pilotare, a controllare il mux (17). Nel
frattempo, durante questo test lALU star facendo loperazione di
somma tra NPC(passato dal primo mux davanti allALU) e
limmediato(passato dal secondo mux) e il risultato sar salvato in
ALU output che verr inviato sempre al mux (17).

Quindi il mux (17) gestito da Cond far passare ALU output(che contiene
lindirizzo di salto calcolato dall ALU) se la condizione stata verificata e
quindi bisogna saltare, se invece la condizione non si verificata e quindi
non si deve saltare quel Cond far passare dal mux (17) NPC. Come vediamo
questi valori che passano dal mux(17) vanno ad aggiornare appunto il PC.

Cosa succede nel quando si cerca di fare un branch confrontando dei registri? Non confrontando il registro
con 0. Bisognerebbe usare l'ALU sia per fare la differenza tra i registri e sia per calcolare l'indirizzo a cui
saltare. Questo tipo di confronto non pu essere fatto con il modello che descritto precedentemente, non si
possono far fare due cose contemporaneamente all'ALU. Quindi bisogna aggiungere un altro modulo che si
chiama Comparatore, e integriamo questo modulo con lo schema che abbiamo studiato precedente.

Prelevati A e B, vengono messi in ingresso al comparatore e l'output di questo pu essere 0 se i due ingressi
sono differenti, 1 se i due ingressi sono equivalenti.

49

Questo output lo si pu far entrare in un altro modulo che in caso di BNE salta se i due registri sono diversi,
quindi scrivendo 1 nel latch cond. altrimenti se invece listruzione una BEQ, fa l'esatto contrario mette 0
nel latch se dal comparatore uscito 1. L'unit di controllo se una BNE imposter il selettore del MUX al
valore 1, altrimenti 0; in quest'ultimo caso, bisogna saltare quando i due registri sono uguali, dal
comparatore uscito 1, quindi eseguiamo il salto.
Un comparatore che dica 0 se sono diversi e 1 se sono uguali si configura come una XOR negata in uscita.
Per uno XOR confronta una coppia di bit,e siccome i registri sono di 64-bit bisognerebbe mettere 64bit per
ogni ingresso e poi avere un'uscita che di un solo bit. Ovviamente, l'uscita 1, solo se tutte le coppie di bit
sono uguali, se anche solo una delle coppie diversa produce uno 0 in output, l'output sar 0, perch
vorrebbe dire che i registri sono diversi.

3. Meccanismo Di Funzionamento Interno Dell'ALU:


Per fare lo schema di una ALU bisogna preoccuparsi di capire cosa va inserito in questo circuito. L'ALU riceve
in ingresso una coppia di dati e il processore deve essere pronto a fare tutto quello che previsto nel set
istruzioni della macchina. Il modo pi semplice per far operare il processore potrebbe essere quello di far
fare a tutti i moduli nell'ALU quello per i quali sono stati creati. In pratica i due ingressi vengono inviati a
tutti i moduli dell'ALU(in parallelo). Se i moduli sono molto diversi, possiamo pensare di integrarli
fisicamente in maniera diversa. Se due operazioni sono abbastanza simili possiamo integrarli in un unico
modulo(per risparmiare dell'hardware). A noi per di tutti i risultati dei moduli presenti nella ALU ne serve
solo uno, infatti la ALU ha una sola uscita, tutte le uscite quindi, vanno in ingresso ad un'unit che decide
quale di queste uscite costituir l'output dell'ALU. L'informazione che permette di scegliere quale uscita
mettere in output all'ALU contenuta nel Codice Operativo dellistruzione.
Ma il codice operativo non controlla direttamente il mux presente nella ALU (Mapping diretto: a 0
corrisponde la somma a 1 la moltiplicazione, a 2 la divisone ecc.) ,non esiste una corrispondenza di uno a
uno tra codice operativo e i moduli dellALU che
effettuano le operazioni, questo perch la ALU non fa
solo l'operazione di somma tra 2 registri solo quando
listruzione una ADD, ma anche nel caso di un
BRANCH, somma di limmediato a un registro. Ad
esempio la somma va associata ad una serie di
codici operativi. Quindi va preso il codice operativo,
inserito in una rete di codifica la quale ricever gli n
bit del codice operativo(di solito 6) e alla sua uscita
50

avr un numero di bit. Questo numero di bit dipende dai moduli presenti nella ALU, ad esempio se ci sono 4
moduli nella ALU avremo bisogno di 2 bit.
Quindi, si hanno una serie di ingressi(ad esempio a 64 bit) e un'uscita con altrettanti bit, quindi ci sono
log2N fili che codificano un numero associato a questi ingressi, questo numero non indica altro che
l'ingresso che bisogna considerare(N dipende sempre dal numero di moduli predisposti nell'ALU).
Se per esempio il sommatore l'ingresso zero, la rete sar una rete combinatoria che per tutti i codici
operativi che richiedono la somma deve produrre in uscita zero. (Questa rete rappresenta un Decoder,
richiami di elettronica digitale con riferimento al decoder a 7 segmenti).
A livello teorico, il codice operativo, dice al MUX quale uscita prendere. In questo modo per ci sarebbe una
corrispondenza 1 a 1, in realt abbiamo spiegato che va inserito un modulo di decodifica.
Qual il problema di un'ALU che lavora in questo modo? Innanzitutto che il tempo in cui l'ALU produrr il
risultato dato dal Tempo del cammino critico. Il cammino critico, ripetiamo, costituito dal massimo
tempo di latenza dei moduli in parallelo sommato a tutti i moduli in serie, in pi va anche aggiunto il tempo
della rete di controllo. Ricorda che un MUX ha una latenza proporzionale agli ingressi.
Sia per risparmiare le batterie di un portatile, che per evitare problemi di riscaldamento, si potrebbe
intervenire con un modulo particolare, che serve ad abilitare o disabilitare i vari moduli(evitando sprechi di
energia). Questi moduli prendono il nome di Moduli Enable che servono per attivare di volta in volta solo i
moduli che servono, ad esempio se in uscita all'enable c' 1, allora il modulo lavora, altrimenti sta fermo. In
questo modo cambia anche la rete di decodifica, perch non serve pi il log2N. Ma ci saranno 64 linee di
uscita, delle quali solo una sar attiva. In questo modo, il controllo pi complicato, ma ci permette di
risparmiare energia. Questo ci permette di eliminare un multiplexer, passando ad un sistema di linee tristate. O meglio, le linee sono tutte collegate in uscita tra di loro, e vengono messe ad alta impedenza le
linee che non servono. Cos facendo solo la linea che ha avuto l'enable collegta fisicamente all'uscita. In
pratica, un sistema tri-state prevede pi linee di uscita(64 in questo caso) tutte collegate tra di loro;
sfruttando l'enable possibile mettere tutte le linee con l'enable impostato su 0, ad alta impedenza.
Ovviamente solo una linea di quelle 64 rimarr a bassa impedenza e produrr luscita.

51

52

CAPITOLO 7
LA PIPELINE

1. Introduzione alla Pipeline


Nel capitolo precedenti abbiamo diviso in 5 stadi la struttura del processore:

1) Reperimento dellistruzione (IF Instruction Fetch)


2) Decodifica dellistruzione/ Reperimento dei registri (ID Instruction decode)
3) Esecuzione dellistruzione/Calcolo dellindirizzo (EX Execution)
4) Accesso alla memoria (MEM)
5) Scrittura (WB Write-back)
Possiamo a questo punto introdurre l'idea di Pipeline, che consiste nel strutturare il processore come un
tubo. Quando viene eseguita una istruzione non ci si deve preoccupare solo del tempo impiego per
quell'istruzione, ma interessa che il processore esegua un intero programma in un tempo minore e che
questa cosa valga per molti programmi.
In pratica ci interessa il tempo che intercorre tra l'esecuzione della prima istruzione e l'esecuzione
dell'ultima.
Normalmente si pensato alla realizzazione di un sistema che prevede il processore impegnato da
un'istruzione e quando ha finito di eseguire quell'istruzione, il processore libero di eseguirne un'altra .
Quindi lo si pu immaginare come un tubo, dove appena terminata lesecuzione della prima istruzione,
questa si sposter facendone entrare un'altra.
Nell'architettura pipeline se una data istruzione ha liberato
determinate risorse, possiamo gi iniziare a caricare
un'altra istruzione e farle usare quel pezzo di processore
che si appena liberato e nel frattempo l'esecuzione della
prima istruzione sta continuando. Perch questo funzioni,
occorre che non ci siano conflitti strutturali gi affrontati
nel capitolo precedente. Quindi in questo momento quello
che interessa solo che, quando la seconda istruzione entra nel processore, le risorse a cui deve accedere
non servino pi alla prima istruzione; se questo non accade si incorre in conflitti strutturali.
La pipeline una trasposizione della catena di montaggio di Henry Ford.

Quindi, prelevata l'istruzione dalla memoria e messa nell'IR e incrementato il PCsi pu gi iniziare a caricare
un'altra istruzione con il PC aggiornato. Allora, supponendo di avere due istruzioni, istruzione 0 e istruzione
1; una volta caricata l'istruzione 0, si pu gi caricare l'istruzione 1 perch l'istruzione 0 ha liberato la

53

risorsa PC, che serve appunto per caricare l'istruzione. Questo serve per diminuire i tempi di esecuzione,
perch di volta in volta non c bisogno di arrivare al termine dell'esecuzione di ogni istruzione per iniziare
ad eseguirne un'altra.
Ora cerchiamo di capire in quale istante termina il programma.
La valutazione per ora approssimativa e consideriamo delle semplificazioni di ci che accade nel
processore.
Ci poniamo una domanda: Il tempo finale di una architettura pipeline quanto vale rispetto al tempo finale
ottenuto da un'architettura non-pipeline?
Supponiamo di avere N istruzioni e per semplicit supponiamo che abbiano tutte la stessa durata, in realt
non esattamente cos perch alcune istruzioni hanno durata diversa. Supponiamo che la durata di ogni
istruzione sia t, allora il tempo finale sarebbe T = N * t.
Anche se questo non totalmente vero, perch anche se le istruzioni possano avere tutte la stessa durata,
non necessariamente il processore impiega lo stesso tempo ad eseguire le istruzione, perch ad esempio
un'istruzione potrebbe gi essere nella cache, un'altra istruzione che non nella cache impiegher pi
tempo ad essere eseguita poich bisogna aspettare che il processore carichi quell'istruzione.
Ora, consideriamo il caso in cui non ci siano stalli di memoria: momenti in cui il processore si ferma perch
non pu reperire dalla la memoria un istruzione o un dato, oppure non si pu scrivere in memoria perch
nella cache non c' la pagina in cui scrivere.
Nel processore in genere si tende a far durare tutte le fasi dell'istruzione nello stesso modo, perch
altrimenti il sistema non viene velocizzato gran che, ad esempio se la fase di fetch dura 1 sec e la fase
successiva ne dura 10, la seconda istruzione verr caricata dopo un secondo, ma non potr passare alla
seconda fase poich le risorse di questa sono ancora impegnate dalla prima istruzione che non ha
terminato, ha bisogno di 10 sec. Il vantaggio di questo adattamento delle fasi apprezzabile nel caso
dell'esecuzione di molte istruzioni; l'adattamento in genere si fa in base alla fase pi lenta, perch quella
che tiene le risorse impegnate per pi tempo.
In questo modo, aumenta il tempo di esecuzione della singola istruzione, ma diminuisce il tempo di
esecuzione di molte istruzioni in ottica Pipeline.
Come abbiamo gi detto, consideriamo N istruzioni e che la durata di una singola istruzione sia t. Il periodo
complessivo, il tempo di calcolo per un processore non pipeline pari

54

Lesecuzione di una istruzione suddivisa, come abbiamo visto, in pi fasi, precisamente sono necessarie 5
fasi. Nella figura per comodit sono state rappresentate solo 2 fasi.
Ipotizzando che la durata di tutte le fasi sia uguale, ogni fase dura

Dove nf il numero delle fasi, nel nostro caso 5; e t la durata di una istruzione.
In questo caso in cui tutte le fasi hanno uguale durata, si parla di Pipeline Bilanciata.
Se invece le fasi non hanno tutte la stessa durata, pipeline non bilanciata, bisogna far durare ogni fase
quanto la fase pi lenta. La singola istruzione nellipotesi in cui non bilanciata, ed costituita da 2 fasi, per
comodit, con durata pari rispettivamente a 1 e 10 durerebbe 20. La durata della fase pi veloce viene
portata alla latenza della fase pi lenta.

La singola istruzione ha aumentato la sua durata rispetto allarchitettura non pipeline ma, considerando
tutte le altre istruzioni in pipeline la durata complessiva migliorata.

55

Facciamo lesempio di una pipeline non bilanciata,sempre con 2 fasi di durata 3 e 7. Bisogna portare la fase
con durata minore ad una latenza pari a quella della fase pi lenta, in questo caso la fase da 3 va portata ad
una latenza pari a 7. La pedata come vediamo pari a 7.

La durata complessiva sar quindi pari alla somma


delle pedate, che sono N, moltiplicata per la durata
di ogni pedata, che vale 7. Inoltre bisogna
sommare la prima fase della prima istruzione che
non conteggiata nelle pedate. In conclusione la
durata dellesecuzione pari:

Oppure non contando la prima istruzione nel


calcolo delle pedate, adesso N-1, e aggiungendo
infine la durata di tutta la prima istruzione, che vale 14:

Il tempo necessario invece per una architettura non pipeline pari, come abbiamo detto, a:

Dove 10 pari alla durata dellistruzione non pipeline, cio quella costituita dalle 2 fasi non bilanciate di
durata rispettivamente di 3 e di 7. t la durata dellistruzione che data dalla somma quindi delle due fasi
3+7. T la durata dellesecuzione, N il numero delle istruzione da eseguire.
Quindi in generale la durata dellesecuzione in pipeline pari a:

Dove con t abbiamo indicato il periodo di una fase, quella pi lenta per una pipeline non bilanciata, che
corrispondete alla pedata nella figura. Il termine t indica la durata di tutta listruzione.
Bisogna per aggiungere al tempo di ogni fase una extradurata necessaria nella pipeline, dovute al fatto
che quando si esegue una istruzione in pipeline bisogna inserire tra uno stadio e laltro delle unit di
memorizzazione(rettangoli stretti e grigi), che verranno analizzati nel prossimo paragrafo, che servono a
contenere dei latch. Queste unit di memorizzazioni introducono una latenza dovuta alle operazioni di
lettura( ) e scrittura( ) su questi.
Quindi la durata complessiva di una singola fase sar pari a:

Dove tmax il tempo della fase con la latenza maggiore.

56

2. Architettura Pipeline
Nella tecnologia pipeline(il cui termine vuol dire canale, condotto, tubatura) il processore viene visto come
un tubo, nel quale le istruzioni entrano non necessariamente attendendo che listruzione precedente sia
uscita. Normalmente si portati a pensare che solo al termine dellesecuzione di una istruzione venga
eseguita la successiva. Nella tecnologia pipeline una istruzione viene immessa nel ciclo di esecuzione
quando ancora non terminata listruzione precedente.
Questo grafico evidenzia come le istruzioni impegnano il processore durante le varie fasi scandite ad ogni
colpo di clock.

La prima istruzione in alto rappresenta lultima istruzione entrata, mentre listruzione pi in basso
rappresenta listruzione che sta per uscire, la prima tra queste ad essere entrata.
Al CC1 (colpo di clock 1) entra una generica istruzione; al CC2 la prima avanza, liberando delle risorse, ed
entra una seconda istruzione e cos via nei colpi di clock successivi. Al CC5 il tubo pieno; listruzione 5
appena entrata nel processore, listruzione 4 nella fase successiva cos come tutte le altre istruzioni fino
allistruzione 1 che si trova allultimo stage e dopo terminer.

57

A un certo colpo di clock entra nel processore


listruzione 5, listruzione 1 passa allultima
fase, listruzione 2 alla penultima fase e cos
via. Al colpo di clock successivo listruzione 1
uscir, listruzione 2 passa allultima fase e
cos le altre, e un un'altra istruzione si
apprester a entrare nel processore. Affinch
questo tubo possa funzionare non possiamo attenerci allarchitettura del processore vista nel capitolo 6,
ma dobbiamo utilizzare un architettura di processore che prevede la tecnica pipeline.
Larchitettura del processore analizzato nel capitolo 6, non si atteneva adeguatamente alle esigenze
richieste dalla tecnica pipeline. Il processore era stato giustamente suddiviso in fasi, e in ciascuna fase
venivano utilizzate risorse che nelle fasi successive non erano usate. Ad esempio analizziamo lincremento
del PC. Questa operazione poteva benissimo essere svolta dallALU, ma come abbiamo visto stato
introdotto il modulo sommatore dedicato al suo incremento. Questo perch se lincremento venisse svolto
dallALU, durante la terza fase, la fase di Esecuzione(EX), dove si richiede luso dellALU, si andrebbe in
conflitto a causa delloperazione di incremento del PC. E anche per queste ragioni che si pensato di
suddividere la memoria in due aree: memoria istruzioni e memoria dati (architettura Harvard). Questa
architettura quindi sembra essere predisposta a supportare una tecnologia pipeline visto che non presenta
conflitti strutturali. Nonostante questo, per, questa architettura non di tipo pipeline. Osserviamo cosa
succede nellesecuzione pipeline: quando entra un istruzione nel processore questa viene scritta nellIR. Nel
colpo di clock successivo questa istruzione procede nella fase successiva, e intanto una nuova istruzione sta
entrando nel processore, andandosi a scrivere nellIR e cancellando il valore scritto prima, quello relativo
alla prima istruzione ad essere entrata. Ora non c pi il codice operativo della prima istruzione, e nelle fasi
successive non si sapr pi che cosa fare! Caricata una istruzione nellIR, ci si prepara a eseguire le prossime
fasi, ma successivamente viene caricata nellIR un'altra istruzione e si perde traccia della prima. Un morto
dimenticato ..celebriamo la giornata della memoria delle istruzioni!
Con questo tipo di architettura nascono problemi, come per lIR, anche sui valori degli Immediati: il latch
Imm viene sovrascritto anchesso; ci sono problemi anche nelle Load e nelle Store poich vengono
sovrascritti i valori di LMD quando vengono eseguite due istruzioni LOAD una di seguito allaltra; anche il
latch B viene sovrascritto quando vengono eseguite due STORE, questa volta, una dopo laltra.
Questa architettura presenta una serie di problemi per la gestione della pipeline. Sono necessarie zone,
aree, dove conservare i dati che di volta in volta vengono prodotti dalle varie istruzioni (la giornata della
memoria!). Un processore per la pipeline molto simile a quello analizzato nel capitolo 6 ma prevede
linserimento di moduli che contengono delle unit di memorizzazione.

58

Questa figura rappresenta il modello dellarchitettura del processore pipeline.

Come vediamo in questa architettura sono stati eliminati i latch come IR,NPC, Imm, A,B, ALUOutput, LMD e
sono stati inseriti dei moduli, quei rettangoli stretti e lunghi in grigio, che sono delle unit di
memorizzazione. Come si nota lIR stato integrato nel primo modulo a sinistra, anche NPC non c pi
perch stato integrato in questo stesso modulo. I latch A,B, Imm sono stati tutti integrati invece nellaltro
modulo, il secondo a partire da sinistra. Tutti i latch sono inseriti in uno stadio intermedio fra due stadi
adiacenti. Uno stadio che sostanzialmente contiene dei latch. Sar necessario replicare questi registri,
questi latch, in tutti questi quattro moduli. Ci saranno una serie di repliche che scorrono lungo il processore
durante tutte le fasi di esecuzione dellistruzione. Il processore si porter tutti questi latch avanti per avanti
in questi moduli. Ad esempio caricata una istruzione nellIR e al prossimo colpo di clock quando verr
caricata listruzione successiva, non si perder il valore precedente di IR, perch, con il meccanismo di shift
register, IR passer dal primo modulo al secondo modulo, si replicher.
Spieghiamo il meccanismo dello shift register. Un normale registro quando riceve un dato lo memorizza, e
quando viene interrogato(lettura dal registro) lo restituisce, conservandolo sempre al suo interno. Quando
viene letto un dato da un registro il contenuto resta sempre nel registro fin quando non viene inserito un
nuovo dato. Lo shift register invece un registro che nel momento in cui riceve un dato(aggiornamento del
contenuto del registro) quello che conteneva viene propagato nella cella adiacente. Il meccanismo simile
a una coda: a ogni colpo di clock i dati nella prima cella vengono trascritti nella seconda soltanto dopo che
quelli della seconda saranno trascritti nella terza e cos via. Solo il dato dellultima cella verr perso.
Questi moduli sono gestiti proprio con un meccanismo di shift register. Quando viene inserita una nuova
istruzione, lIR del primo modulo viene prima copiato(shiftato) nellIR del secondo modulo, e poi viene
sovrascritto dalla nuova istruzione. E quindi nella fase di esecuzione, in cui si dovr andare a fare
loperazione prevista dallistruzione, verr letto il codice operativo dellistruzione che si trova nellIR del
secondo modulo.

59

2.1. La Tipica Vita Di Unistruzione In Pipeline


Analizziamo il percorso che compie unistruzione. Consideriamo lavvio, lesecuzione di una istruzione.
Durante un certo colpo di clock (parte il periodo di un colpo di clock) avviene la fase di fetch di una
istruzione( PC > memoria istruzioni > IR). Soltanto al termine di questo colpo di clock, listruzione viene
scritta nellIR. Al termine del colpo di clock in IR c scritta listruzione di cui stiamo analizzando la vita.
Parte il secondo colpo di clock. LIR del primo modulo, che contiene listruzione prelevata prima, trasferisce
il suo contenuto nellIR del secondo modulo. LIR del primo modulo viene copiato nellIR del secondo
modulo. Poich questa operazione abbastanza veloce, il contenuto di IR c ancora nel primo modulo,
non stato ancora sovrascritto, verr sovrascritto solo al termine di questo colpo di clock, quando verr
letta laltra istruzione. Durante questo colpo di clock quindi avviene la decodifica dellistruzione e quindi la
precarica degli operandi: prelevo dal banco dei registri A e B, ed estendo in segno gli Immediati. Terminato
il secondo colpo di clock nel vecchio IR, quello del primo modulo, non c pi la prima istruzione ma quella
nuova, mentre la prima si trova adesso in IR del secondo modulo. Al terzo colpo di clock listruzione(la
prima in assoluto) viene eseguita, fase di EX. Se listruzione deve fare per esempio la somma tra un registro
e un immediato, lUnit di Controllo legge il codice operativo dellIR che si trova nel modulo 2 e prepara
lALU per fare la somma tra A e limmediato. Allora i due multiplexer davanti agli ingressi dellALU faranno
passare ,opportunamente gestiti dallunit di controllo, A e lImmediato che erano memorizzati nel secondo
modulo, e il risultato viene salvato in ALU output che si trova nel terzo modulo. Intanto listruzione
successiva giunta al secondo modulo(i nazisti sono arrivati al secondo modulo!) ma IR stato gi copiato
nel terzo modulo, altrimenti non si saprebbe pi cosa fare con lALU output. La stessa cosa avviene per A e
B. B viene a copiarsi nel terzo modulo.
Quindi fase dopo fase vanno replicati ogni volta i latch da un modulo al modulo successivo.
Mentre nel processore non pipeline cera un solo IR, di cui si sapeva dove era, chi era e che faceva, cos
come tutti gli altri latch, nel processore pipeline ci sono diversi IR, diversi B, diversi Imm, diversi LMD,
perch sono stati replicati nei quattro moduli.
Per non confonderci, per identificare ciascuno di questi latch, per sapere di quale IR, di quale B, di quale
LMD stiamo parlando, a quale di questi ci stiamo riferendo, diamo un nome a questi utilizzando questa
notazione:
Nome modulo
IF/ID
ID/EX
EX/MEM
MEM/WB

Seguito dal . e il nome del latch, intendendo cos che ci stiamo riferendo a questo latch che si trova in
questo modulo. Ad esempio con IF/ID.IR intendiamo il latch IR del modulo IF/ID, cio ci stiamo riferendo al
latch IR che si trova tra lo stage di IF e lo stage di ID. Con EX/MEM.IR stiamo focalizzando lIR che contiene
listruzione pronta a eseguire la fase di MEM. IR si propaga nei diversi moduli quindi avremo anche ID/EX.IR
e MEM/WB.IR
Gli altri latch saranno: ID/EX.A, EX/MEM.B oppure MEM/WB.B e cos via anche per Imm, LMD e NPC
60

3. Microcodice o Microistruzioni
Vediamo come si eseguono le istruzioni in un processore pipeline passo dopo passo attraverso delle
microistruzioni, o microcodice.

Questa figura rappresenta, con un linguaggio comprensibile, microcodice, quello che avviene allinterno del
processore durante lesecuzione di una qualsiasi istruzione. Sono rappresentate le cinque fasi e viene
descritto ci che avviene in ogni fase. Ci accorgiamo che nella fase EX ci sono 3 colonne che differenziano il
61

comportamento dellistruzione a seconda del suo tipo, mentre nelle prime fasi non avviene questo, perch
nella fase di IF o nella fase ID non si conosce ancora il tipo dellistruzione, bisogna aspettare la decodifica
infatti. Fino al decode quindi si fanno cose uguali per tutte le istruzioni e speculative(precarica degli
operandi, viene fatta una cosa che pu essere utile oppure no).
La differenza del processore non pipeline che oltre a svolgere il compito che quella fase prevede bisogna
gestire la fuga dal ghetto di Varsavia: gli spostamenti delle varie informazioni che altrimenti verrebbero
perse per larrivo di altre istruzioni nel processore(arriva la pattuglia di Nazisti!).
Tutto quello che avviene in ogni fase, avviene in un colpo di clock. Il colpo di clock deve essere tale da
permettere lesecuzione di ogni fase, dimensionato sulla latenza della fase pi lenta.
Dividiamo il periodo di clock in due semifasi. Nella prima semifase avviene la lettura dei registri e dei latch,
mentre nella seconda semifase avviene la scrittura di questi.

Analizziamo ora ogni fase:


Premettiamo che tutto quello che avviene in questa tabella avviene contemporaneamente, cambiano solo
le istruzioni su cui si agisce, ogni fase agisce sempre su una istruzione diversa. Tutte queste microistruzioni
(o microcodice) avvengono tutte nello stesso tempo, contemporaneamente.

3.1. IF (ISTRUCTION FETCH)


Durante la fase di Istruction Fetch, come nel caso di un processore non pipeline, si legge il PC, si estrae
dalla memoria istruzioni listruzione relativa al PC e la si memorizza in IR. Un attimo dopo aver letto il PC e
averlo mandato in memoria, lo si incrementa.

62

Quindi nellIR del banco IF/ID viene scritto il componente del vettore Mem(memoria indirizzi) puntato da
PC e nel fra tempo viene aggiornato il PC, che pu provenire da due possibili alternative, o PC+4 nei casi
normali, oppure il valore dove saltare quando c una istruzione di salto jump o branch, in questultimo
caso(branch) PC verr aggiornato con lindirizzo di salto solo se la condizione di branch stata verificata,
altrimenti deve continuare a valere PC+4.
Nel secondo rigo viene quindi verificato se opcode dellIR del modulo EX/MEM una istruzione di tipo
branch, e se vero viene verificata la condizione, cio cond del modulo EX/MEM, e se questa vera in PC e
in NPC andr il risultato di ALU output(indirizzo di salto), altrimenti PC+4.
Non codificato il caso di JUMP, e listruzione potrebbe essere modificata in questo modo:
IF/ID.NPC, PC (if(((EX/MEM.opcode == branch) &
EX/MEM.opcode == jump) {EX/MEM.ALUOutput} Else {PC+4});

EX/MEM.cond)

||

Oppure si pu pensare che la branch sia anche una jump per con il latch cond settato automaticamente a
1, in modo da avere la condizione sempre vera, ed effettua sempre il salto.
Notiamo che PC non in quei moduli, un registro esterno, per questo non identificato con IF/ID.
Ricordiamo che la lettura dei latch avviene nella prima semifase del periodo di clock, per cui il PC nella
prima semifase viene letto e trasferito alla porta indirizzi della memoria. Mentre nella seconda semifase, in
cui avviene la scrittura dei latch, PC riceve ci che c scritto al secondo rigo. Il PC quindi instrada la
memoria istruzioni per anche prima che questa memoria tiri fuori listruzione, PC viene aggiornato, anche
se la memoria non ha ancora dato listruzione, ma non fa niente, PC il suo lavoro lo ha gi svolto.
Notiamo che oltre a sovrascrivere il PC si sta aggiornando NPC che si trova nel modulo IF/ID.
A questo punto parte la decodifica dellistruzione che terminer alla fine della fase ID.
3.2. ID (ISTRUCTION DECODE)
Nel primo rigo avviene la copia dei registri in A e B: lelemento del banco dei registri(Regs) che interessa
IF/ID:IR[rs], cio quello indicizzato dal valore rs(che un numero) viene copiato in ID/EX.A e cos il registro
IF/ID:IR[rt] viene copiato in ID/EX.B.

63

Nel secondo rigo vengono trascritti nel modulo successivo il valore di NPC e di IR: vengono copiati NPC e IR
dal modulo IF/ID in NPC al modulo ID/EX. ATTENZIONE:
Trascrizione moduli NPC e IR
Nella prima semifase in ID:
lettura IF/ID.NPC
lettura IF/ID.IR
Nella seconda semifase in ID:
scrittura ID/EX.NPC
scrittura ID/EX.IR
Nella seconda semifase in IF:
sovrascrittura IF/ID.NPC e sovrascrittura IF/ID.R con i nuovi valori
per la nuova istruzione
Nel terzo rigo(ricordiamo che queste righe avvengono tutte in simultanea!) avviene lestensione in segno
del campo immediato in ID/EX.Imm.
Al termine di questa fase terminata la decodifica dellistruzione.
3.3. EX (EXECUTE)
Decodificata listruzione si viene a conoscenza del tipo delloperazione che si andr ad eseguire e quindi sia
in questa e sia nelle prossime fasi ci sar una distinzione delle microistruzione a seconda del tipo di
istruzione che si sta per eseguire, che come abbiamo detto precedentemente si distinguono in istruzioni
della ALU, istruzioni load o store e istruzioni di diramazione(salto).
Durante queste istruzioni avviene comunque la trascrizione dellIR dal modulo ID/EX al modulo EX/MEM.

Istruzione della ALU: ci sono due modi differenti di operare a seconda del tipo di operandi;
o Due operandi registri : in ALUOutput di EX/MEM viene memorizzato l risultato
delloperazione(somma, prodotto ecc.) tra ID/EX.A e ID/EX.B , quindi un operazione tra i
due registri
o Un operando registro e un operando immediato: in ALUOutput ci sar il risultato
delloperazione tra EX/MEM.A e limmediato ID/EX.Imm.
Istruzione load o store: Nelle load e le store lALU viene utilizzato per calcolare lindirizzo di
memoria, e il calcolo avviene tra un indirizzo e un immediato. In ALUOutput quindi va sempre la
somma tra A e limmediato. Oltre a questo trasferisco IR e il latch B dal modulo ID/EX al modulo
EX/MEM. In realt il trasferimento di B potrebbe non farsi se si sta facendo una Load, ma gestire

64

questa cosa cio se Store trasferisci B e se una Load fregatene una cosa che complica la vita, si
porta avanti B comunque piuttosto che portarlo solo se serve.
Istruzione di diramazione: Quando c un salto lALU viene utilizzata per calcolare lindirizzo dove
saltare. Nella fase IF nel PC eventualmente invece di inserire PC +4, si va a mettere ALUOutput che
si trova in EX/MEM il cui valore si ottiene dalla somma tra NPC che si trova in ID/EX.NPC (che
contiene PC+4) e limmediato ID/EX.Imm per shiftato a sinistra di due posti poich nei salti non si
codificano i due zeri meno significativi dovuto al fatto che lindirizzo multiplo di quattro.
LImmediato esteso in segno diventa laddendo a NPC. Nellultimo rigo op indica i vari test su cui
condizionato il test(==, !=) . Non presente il confronto tra i registri. Listruzione di salto termina
nel momento in cui viene aggiornato il PC. Non c la fase di MEM o di WB. In questo caso lIR non
c bisogno di trascriverlo visto che il ciclo di vita di questa istruzione termina qui..

3.4. MEM

Istruzione della ALU: Il risultato calcolato nella fase precedente, memorizzato in ALUOutput di
EX/MEM, deve essere trasferito sempre in ALUOutput per del modulo MEM/WB. Analogamente si
trasferisce IR da EX/MEM a MEM/WB, altrimenti viene perso il registro e si deve salvare il risultato
che codificato nellistruzione, non si pu ancora perdere IR. In questo caso la fase MEM consiste
nella trascrizione dei dati: il risultato e lIR.
Istruzione load o store: C una distinzione tra Load e Store, viene comunque propagato IR in
entrambi i casi:
o Load: c un accesso a memoria in lettura, e viene memorizzato il dato che si trova in
memoria allindirizzo di ALUOutput in LMD.
o Store: accesso alla memoria in scrittura. Viene scritto il valore B nella memoria indirizzata
da ALUOutput.

3.5. WB

65

Istruzione della ALU: Viene scritto il risultato di ALUOutput nel registro, nel banco dei registri
indicizzato dai bit che sono nel campo rt dellIR che stato trascritto prima in MEM/WB. Il risultato
viene scritto nel banco dei registri.
Istruzione Load o Store: Se listruzione una store, come abbiamo visto listruzione termina alla
scrittura in memoria. Quindi la fase di WB eseguita soltanto per una istruzione Load. Il dato
appena letto dalla memoria, che si trova in MEM/WB.LMD e viene inserito nel banco dei registri,
nel registro indicizzato dai bit del campo rt dellIR che si trova in MEM/WB che opportunamente
stato trascritto nella fase precedente.

66

CAPITOLO 8

PRESTAZIONI PROCESSORE PIPELINE


Nei precedenti capitoli abbiamo detto che se un istruzione consta di due fasi di cui la prima , ad esempio,
dura 1 e la seconda dura 9, bisogna portare la fase pi breve ad avere la latenza della fase pi lunga, in
questo caso la fase che dura 1 deve durare 9.
Invece di pensare a una situazione in cui lo stadio pi veloce viene dilatato, si cerca di far durare la fase con
la latenza pi lenta quanto quella pi veloce, cio far durare 1 la fase con latenza 9. Questo possibile
attraverso due tecniche:
1) Parallelismo: aumentare le risorse di una fase per accelerare le prestazioni. Se bisogna far durare 1
una fase che dura 9, mettendo 9 risorse in pi in quella fase, maniera tale che se 1 risorsa esegue
loperazione in una durata pari a 9, nove risorse lavoreranno in 1.
2) Super pipeline: separare la fase pi lenta in sottofasi che durino ognuno quanto la pi veloce.
Suddividere la fase da 9 in sottofasi da 1.
Queste due soluzioni sono entrambi soluzioni che vanno considerate. Analizziamole entrambe partendo
dalla seconda.
1. Miglioramento prestazioni: super pipeline
Suddividiamo la fase pi lunga, che chiamiamo per semplicit B e invece quella pi breve A, in sottofasi,
magari 9 sottofasi da un colpo di clock. Una data istruzione che ha terminato la fase A potr procedere nella
prima sottofase di B perch listruzione precedente a questa avr gi terminato la prima sottofase di B.
Questo il modo pi semplice di risolvere il problema, per nellipotesi che effettivamente questa fase
possa essere suddivisa in 9 sottofasi. Attenzione per che per migliorare le cose non necessario che la
fase B sia divisibile in 9 sottofasi, potrebbe anche essere una cosa positiva, se questa fase pu essere
suddivisa in 3 sottofasi. In tal caso se le tre sottofasi fossero bilanciate, noi avremmo una situazione in cui
invece di avere una latenza ogni 9 colpi di clock adesso diventerebbe ogni tre colpi di clock, con un
accelerazione del 300%. Per perch fattibile se quella fase divisibile in delle sottofasi. In queste ipotesi,
in cui la fase B divisibile in 9 fasi equilibrate, un istruzione che in una pipeline divisa in due stadi diventa
un istruzione strutturata su una pipeline da 10 stadi: super pipeline. Non detto comunque che individuare
in questa fase B 9 sottomoduli casualmente tutti di durata bilanciata pari a 1 e strutturare una pipeline del
genere che si vadano a migliorare le cose. Bisogna considerare che aumentando le fasi si vanno ad
aggiungere dei latch che hanno una durata di lettura e scrittura. Per cui queste sottofasi non saranno pi 9
sottofasi di durata 1, ma 9 sottofasi di durata 1 pi il tempo di lettura e scrittura del latch iniziale e di quello
finale. Quindi affinch la durata di una sottofase valga 1, bisogna pensare di dividere la fase B in 10 fasi,
invece che 9, di durata 0.9 che considerando la lettura e scrittura dei latch si arrivi a una durata totale per
sottofase pari a 1. Fatta questa precisazione e supposto che alla fine anche contando i latch si riesce a stare
in un tempo 1; qual lulteriore problema si viene a generare in questa situazione? Il problema dovuto al
conflitto dei dati.
I conflitti di dato sono dei conflitti che sopraggiungono quando unistruzione necessita di usare un dato, che
il risultato di una certa operazione appena precedente e che quindi non disponibile nel registro perch

67

questa non lo ha ancora calcolato. Analizzeremo le diverse casistiche. Per cui alla fine aver attraversato
questo throughput (tempo di attraversamento del processore con ritmi di ogni istruzione per colpi di clock)
porta poco giovamento perch comunque si dovr aspettare che listruzione precedente calcoli il risultato.
La complicazione effettiva che si paga in un sistema di questo genere data dallunit di controllo. Nelle
lezioni precedenti, quando abbiamo introdotto la Pipeline, abbiamo detto che lunit di controllo
supervisiona simultaneamente lesecuzione delle 5 istruzioni che sono nei 5 pezzi di quel processore. E
evidente che unistruzione strutturata in una pipeline di 10 fasi coster ununit di controllo pi complessa
di una che lavora su una pipeline di 5 fasi. Visto che quante sono queste fasi sono anche le istruzioni
presenti simultaneamente nel processore, lunit di controllo deve gestire molte pi istruzioni in questo
caso. Quindi con una pipeline di 10 stadi significa che, siccome in ogni stadio c un istruzione, in un
determinato momento il processore sta eseguendo 10 istruzioni. Quindi lunit di controllo pi complessa
visto che deve gestire 10 istruzioni contemporaneamente. Inoltre molto pi semplice trovare le
dipendenze fra le istruzioni in una pipeline da 2 fasi, quindi due istruzione, piuttosto che in una da 10
In conclusione se la fase con la latenza suddivisibile in pi sottofasi, si crea una super pipeline che non
sempre una cosa intelligente. Ci sono casi in cui non possibile farlo e altri in cui si pu, per bisogna
valutare se questa soluzione convenga.
2. Miglioramento prestazioni: parallelismo
Analizziamo ora la prima soluzione. Mettendoci nella stessa situazione vista prima, in cui c una pipeline
con due fasi di durata 1, fase A, e 9, fase B. Supponendo che nella fase B ci sia un modulo che per compiere
un certo lavoro necessit di 9 unit di tempo, mettendo 9 moduli si riesce a far durare la fase 1 unit di
tempo. In realt questa cosa va interpretata meglio!!
Se un operaio impiega 9 ore per compiere un certo lavoro, 9 operai impiegherebbero 1 ora
Lesempio degli operai in questo caso non esattamente calzante. Dipende se le operazioni della fase B
sono parallelizzabili o meno.
Consideriamo il caso dellavvitamento di una vite:

Per avvitare la vite si necessita di un certo tempo, cio il tempo in cui essa entri ed effettui il
percorso per arrivare alla fine. Quanto tempo si impiega per avvitare questa vite? Mancano dei
dati. Serve conoscere il passo della filettatura, quanti giri al minuto si effettuano con
lavvitatore. Supposto: 1 giro/minuto; il passo della filettatura 1 cm e bisogna percorrere 10
cm, allora evidente che servono 10 minuti per avvitare tale vite. Ora mettendo 10 operai di
certo non si risparmia tempo! Diverso se bisogna avvitare 10 viti: operazione parallelizzabile.
Non sempre quindi il processo parallelizzabile.
Per si pu sfruttare un meccanismo in cui duplicando la risorsa, non si abbatte il tempo della singola fase,
ma si abbatte il tempo di ingresso di una nuova istruzione nella pipeline, il tempo in cui nella pipeline si pu
inserire un nuovo Task. Anche se il processo non parallelizzabile si possono utilizzare, ad esempio, 9 ALU
che compiono la fase B , ciascuna di queste fa quello che deve fare in 9 unit di tempo. Escludo che questi 9
lavori tutti sulla stessa istruzione, perch il task non detto che sia affrontabile in parallelo. Se
affrontabile in parallelo allora si pu pure pensare a un caso come detto prima in cui cerano 9 viti da
68

avvitare, utilizzando 9 operai, ciascuno avvitava una vite, e si va al ritmo 1 piuttosto che 9. Poich questo
non sempre possibile, perch loperazione da fare non sempre parallelizzabile, si replica questa risorsa
e, listruzione, terminata la fase A, entra allo stadio B. Ora questa istruzione impiegher, utilizzer, una delle
9 ALU messe a disposizione per 9 unit di tempo(quanto la durata sempre della fase B). Al prossimo colpo
di clock, unaltra istruzione entrata nel processore, terminata la fase A, si accinge a entrare nella fase ;
questultima istruzione verr destinata a un'altra ALU a disposizione, di certo non allALU di prima visto che
occupata ancora dallistruzione appena precedente. Questo avviene per i nove colpi di clock successivi. Al
10 colpo di clock, in cui unaltra istruzione si accinge ad entrare nella fase B, le ALU sembrerebbero tutte
occupate, ma la prima istruzione in assoluto che abbiamo discusso, ha avuto nove colpi di clock per fare il
suo lavoro e quindi uscir dal processore liberando lALU, pronta ad essere nuovamente occupata
dallistruzione di questo colpo di clock. In sostanza il processore lavora con un ritmo di un istruzione per
colpo di clock, la fase B con 9 moduli che lavorano con un ritmo di 9 colpi ciascuno. Al termine di questi 9
colpi di clock, la prossima istruzione pu andare di nuovo allinizio.
In sostanza, si gestito in parallelo un processo pi lento in maniera tale da avere un throughput
trasparente per lo stadio precedente, perch lo stadio precedente non se ne accorto che la fase B ci
mette 9 unit di tempo a compiere il suo lavoro perch per lui ce ne mette uno. Il processore lavora con un
ritmo di un colpo di clock alla volta.
Il costo di questa soluzione , intanto, di aver messo 9 di questi oggetti (9 ALU) e poi gestire un
meccanismo di instradamento a una corsia piuttosto che a un'altra. Questo avviene anche al casello
autostradale: se mediamente arriva una macchina al minuto, se io gestisco loperazione del casello in 10
minuti, mettendo 10 caselli non c coda se chi arriva sufficientemente intelligente di capire qual il
casello che si liberato.
Per realizzare questo a livello di hardware si utilizzare un dispositivo che funziona con la logica contraria del
Mux, si chiama DeMux (de multiplexer).

Il Mux riceve tanti ingressi e poi decide in base al selettore quale tra questi deve andare in uscita. Il DeMux
(Demultiplexer) invece ha un ingresso e tante uscite e decide su quale uscita pilotare questo ingresso.
Come il Mux ha un controller, il DeMux ha un contatore (contatore modulo N). Un contatore un oggetto
che ha un ingresso e un uscita che in sostanza un numero. Il contatore controlla su questo ingresso se
arrivano impulsi, e ogni volta che ne rileva uno scatta. Si da un numero massimo, e arrivato a quel numero
si riparte da 0. Quindi per esempio modulo 8 significa che invece di scrivere 8 mi scrive di nuovo 0. Conta gli
eventi, possono anche essere infiniti ma luscita non il numero dellevento di per s, altrimenti avrei
bisogno di infiniti bit per esempio, ma il numero dellevento diviso 8 e di questa divisione si prende il
resto(operazione modulo). Quando dato un certo numero di eventi si effettua loperazione modulo N di
69

questo numero di eventi, il risultato rappresenta luscita del contatore. Quindi considerando loperazione
modulo 8, si avranno uscite che vanno da 0 a 7.

70

CAPITOLO 9

CONFLITTI NELLA PIPELINE

Analizziamo cosa accade durante l'esecuzione di un programma colpo di clock per colpo di clock.

Dividiamo lo schema del processore pipeline in righe e colonne, dove sulle righe troviamo le fasi
dellistruzioni nel processore e sulle colonne sono rappresentati i colpi di clock del processore: fasi
istruzioni per colpo di clock.

Notiamo una specie di cortocircuito (che non tutte le fasi hanno, ci sono solo nella
fase ID in cui vanno in ingresso i dati allALU in quel caso limmediato, nella fase EX per
inviare il latch B direttamente alla fase successiva, e nella fase MEM per andare
direttamente in WB senza passare per memoria dati DM, Data Memory).

Come si nota il blocco dei registri nella fase di ID tratteggiato a sinistra e continuo a destra, mentre nella
fase di WB continuo a sinistra e tratteggiato a destra. Analizzando colpo di clock per colpo di clock,
notiamo che nel primo colpo di clock non c' nessun pericolo di conflitto, cos come nel secondo colpo di
clock cos come negli altri fino al quinto(il fatto che nei primi quattro colpi di clock non ci siano problemi

71

dato dal fatto che la memoria stata divida in memoria delle istruzioni e memoria dei dati, evitando i
conflitti strutturali.
Al quinto colpo di clock per, subentra un problema poich la prima istruzione e la quarta istruzione
vogliono lavorare entrambe sul banco dei registri. La prima in realt se fosse unistruzione di tipo jump non
avrebbe bisogno di scrivere sul registro(istruzioni che non eseguono la fase di WB), e listruzione sarebbe
terminata prima. Nel caso in cui la prima istruzione ha la necessit di scrivere, quindi accedere al banco dei
registri, nasce un problema dovuto al fatto che anche la quinta istruzione, in contemporanea alla prima
(steso colpo di clock), necessita di accederci in lettura. Fortunatamente le durate delle due fasi sono state
dimensionate sulla fase pi lenta( che potrebbe essere o la fase di Fetch o la fase di MEM) e la fase pi
lenta abbastanza ampia da permettere in maniera seriale 2 accessi al banco dei registri. Quindi, accede la
prima istruzione per fare il write back(prima met del colpo di clock) e poi accede la quinta istruzione per
fare la precarica degli operandi(seconda met del colpo di clock). Ricordiamo per che se si decide che la
precarica degli operandi avviene nella seconda met del colpo di clock, questo vale per tutte le istruzioni,
altrimenti genero altri conflitti, oltre al fatto che farei confusione perch le istruzioni non verrebbero
eseguite tutte nello stesso modo.
1. Conflitti di Dato
Nei capitoli precedenti abbiamo introdotto ci che veramente rende un processore pipeline: i latch, e
abbiamo visto quindi come le latenze per ogni fase devono aumentare e il colpo di clock dimensionato
sulla latenza della fase pi lenta, visto che tutte le fasi vengono eseguite in parallelo.
Per adesso, la divisione delle memorie, l'introduzione del sommatore PC+4 e la divisione degli accessi al
banco dei registri hanno evitato conflitti strutturali, quindi sicuramente non avremo conflitti di questo tipo
all'interno del nostro processore.
Occorre per anche gestire i conflitti di dato, conflitti che si verificano nel momento in cui un'istruzione
vuole usare un dato che il risultato prodotto da unistruzione precedete, nasce quindi un conflitto tra le
due istruzioni sullo stesso dato.

72

Supponiamo di avere un programma fatto come nell'immagine, in cui la prima istruzione vuole fare la
somma tra R2 ed R3 e scrivere il risultato in R1, mentre un'altra istruzione esegue la differenza tra R1 ed R5,
poi un'altra istruzione utilizza R1 come operando di unoperazione AND e un'altra istruzione ancora che
vuole usare R1 per un operazione OR.
1. DADD R1,R2,R3
2. DSUB R4,R1,R5
3. AND R6,R1,R7
4. OR R8,R1,R9
5. XOR R10,R1,R11
Quindi R1 input per tutte le istruzioni che seguono. Questo un esempio limite che per mette in
evidenza tutti i problemi che si possono incontrare durante un'esecuzione pipeline di un programma del
genere(i problemi si hanno solo nell'esecuzione in pipeline).
Il problema nasce dal fatto che l'istruzione 1 scrive il risultato in R1 solo nella sua fase di write back, colpo di
clock 5, mentre la seconda utilizza R1 nella sua fase di ID, colpo di clock 3, prendendo un valore di R1 che
non il risultato dell'istruzione di prima, visto che non stato ancora scritto in R1, ed un valore che non
ha significato per la logica del programma. Questo problema nasce in tutte le successive istruzione che
vogliono utilizzare R1 prima ancora che questo sia stato prodotto e scritto. Effettivamente in questo
programma l'unica istruzione che non ha problemi l'istruzione 5 che fa il fetch nel colpo di clock 6,
quando R1 gi stato scritto.
Nel paragrafo precedente si visto che nello stesso colpo di clock si accede ai registri prima per effettuare
la write back e poi per la precarica: nella prima met si esegue la WB e nella seconda la precarica degli

73

operandi. Infatti la write back della prima istruzione, viene eseguita prima della lettura della quarta
istruzione. La quarta istruzione ottiene il valore corretto di R1 proprio perch viene eseguita prima la WB
dellistruzione uno e dopo la precarica dellistruzione quattro; quindi la lettura dellistruzione quattro
prelever il valore di R1 corretto perch stato appena scritto dallistruzione uno. Questo possibile
perch nello stesso colpo di clock, nella prima met avviene la scrittura e nella seconda met la lettura.
Fino ad ora, abbiamo visto che se un'istruzione (2) necessita del risultato di un'altra istruzione precedente
(1), non ci sono problemi se (2) viene eseguita 4 colpi di clock dopo, perch la write back di 1 gi stata
eseguita quando (2) effettua la precarica; e con l'ultima considerazione appena fatta ci siamo accorti che
non ci sono problemi nemmeno se (2) viene eseguita 3 colpi di clock dopo (1) perch la write back viene
fatta nella prima met dello stesso colpo di clock in cui (2) effettua la precarica.
Le cose sono pi complesse man mano che ci avviciniamo all'istruzione che produce R1. Infatti la seconda e
la terza istruzione richiedono R1 prima che la prima istruzione abbia scritto al banco dei registri.
Una soluzione potrebbe essere quella di creare stallo, o meglio fermare l'esecuzione di una determinata
istruzione per un colpo di clock, ma non conveniente perch stallando una determinata istruzione bisogna
necessariamente stallare tutte le altre istruzioni, altrimenti verrebbero generali conflitti strutturali.
Per cui evitare gli stalli molto importante, anche se ci sono degli stalli obbligatori, come lo stallo a causa di
un fallimento di accesso a memoria(ad esempio se la cache non contiene l'istruzione che a questo punto va
caricata dai livelli sottostanti).
A questo punto bisogna trovare una soluzione per eliminare questo stallo.
Per evitare questo stallo, ci serve ricordare che la seconda e la terza istruzione richiedono il risultato che va
scritto in R1. Quindi all'istruzione non serve leggere il dato da R1 ma serve solo il contenuto che dovrebbe
avere R1 al termine della WB dellistruzione uno. Adesso, rinominiamo le 3 istruzioni dalla prima alla terza
con (1) (2) (3). Listruzione (1) produce il risultato, che verr scritto in R1 nella WB, gi al colpo di clock 3 in
uscita allALU: EX/MEM.Aluoutput. SI pu pensare con un meccanismo che prende il nome di
cortocircuitazione dellalu di collegare EX/MEM.Aluoutput dellistruzione (1) allingresso dellALU
dellistruzione (2). Si avverte lALU di utilizzare, invece di R1 preso dal banco dei registri con un valore
errato, il dato appena passato attraverso il cortocircuito. Nella fase di exe di ogni istruzione l'unit di
controllo deve accertarsi che gli input non siano output di istruzioni precedenti, perch altrimenti bisogna
ricorrere ad alternative come quella spiegata prima.
Il meccanismo il seguente, listruzione (2) durante la precarica comunque metter R1 in A; per durante la
fase di EXE lunit di controllo verificher se R1 destinazione dellistruzione precedente, se questo vero
allora invece di inviare A nellALU invier EX/MEM.Aluoutput cortocircuitato.
Avviene lo stesso per listruzione (3), per a questo punto l'unit di controllo oltre a verificare se l'operando
output di (2), deve anche verificare se l'operando output di (1)(come effettivamente ); a questo punto
quindi, in ingresso non prender pi R1, ma prender MEM/WB.ALUoutput perch adesso il risultato che
andr scritto in R1 si trova in questo latch, in EX/MEM.ALUoutput troveremmo il risultato dellistruzione (2).
Analizziamo ora cosa accade quando il Risultato di istruzione Load input di altre istruzioni successive:

74

Supponiamo che ci siano 3 istruzioni, la prima carica R1 dalla memoria, la seconda usa R1 per produrre R4 e
la terza usa sia R1 che R4(il programma nell'immagine non fa altro che spostare un dato). In questo caso il
risultato della load sorgente per una store.
La seconda istruzione porta in ingresso all'ALU EX/MEM.Aluoutput, mentre la terza metter in ingresso
all'ALU MEM/WB.Aluoutput, , come spiegato nel paragrafo precedente. La cosa aggiuntiva di questo
esempio che la terza istruzione usa anche R4 che non un risultato ALU, ma la destinazione di una
Load, quindi nel processore quel valore compare per la prima volta in MEM/WB.LMD(colpo di clock 5) e
quindi la terza istruzione user R4 prelevandolo da MEM/WB.LMD. In questa situazione non nascono
problemi per il semplice fatto che R4 viene scritto in memoria( sorgente si una Store).
Supponendo che la seconda istruzione invece di essere una load fosse un'operazione ALU a produrre R4, in
ingresso all'ALU all'istruzione 3 andrebbe EX/MEM.B(che proviene dall'istruzione 2, sarebbe sempre un
ALUoutput).
Ora abbiamo risolto questi casi senza dover ricorrere a stalli, semplicemente complicando un po' l'unit di
controllo.
Vediamo cosa succede se un'istruzione produce R1 come risultato di una LOAD e R1 input di altre
istruzioni aritmetico logiche successive.

75

Ovviamente listruzione entrata nel processore 3 colpi di clock dopo la prima non avr problemi a prelevare
R1 dal banco dei registri. Listruzione entrata nel processore 2 colpi di clock dopo, che in ingresso vuole il
risultato di un'istruzione che avvenuta 2 colpi di clock prima, che una load in ingresso non avr
MEM/WB.Aluoutput (perch non usa l'ALU!), ma avr MEM/WB.LMD. Nellistruzione 2, quella in ingresso al
colpo di clock 2, il vero valore di R1, che serve all'inizio del colpo di clock 4, non c da nessuna parte
allinterno del processore, in memoria dati, quindi per usare quel valore c la necessit di aspettare che
quel dato venga prodotto all'interno del processore. Quindi necessario stallarsi per un colpo di clock e
leggere R1 al prossimo colpo di clock da MEM/WB.LMD. Questo stallo determina lo stallarsi di tutte le
istruzioni seguenti, altrimenti si verificherebbero conflitti strutturali.
Vediamo ora come fatto l'HW aggiuntivo per gestire questa corto circuitazione dellALU:

76

I Mux che come visto nei capitoli precedenti servivano a far entrare in ingresso allALU i latch A o NPC, e B
oppure limmediato. Con le soluzioni descritte in questo capitolo si dovranno gestire anche altri ingressi:
EX/MEM.Aluoutput collegato a entrambi i mux, perch questo potrebbe andare a sostituire o il primo o il
secondo operando; come possibile altro candidato ad entrare nellALU c EX/WB.ALUoutput, anche
questo collegato a entrambi i mux per il motivo detto prima; e ci sar anche MEM/WB.LMD.
Entrambi i mux saranno controllati da una logica: se il sorgente di questa operazione la destinazione
dellistruzione immediatamente precedente, verifica se si stratta di un operazione ALU o un operazione
LOAD; se una Load bisogner stallare altrimenti in ingresso allALU andr EX/MEM.ALUoutput. Se invece
non destinazione dellistruzione immediatamente precedente verifica se destinazione dellistruzione
prima ancora (due istruzioni sopra); se destinazione verifica se si tratta di un operazione ALU oppure
LOAD. Se si tratta di un operazione LOAD in ingresso allALU andr MEM/WB.LMD, altrimenti
MEM/WB.ALUoutput.
Questo vale sia per il latch A e sia per il latch B.
Una cosa analoga vale per le istruzioni store, in cui bisogna decidere se in ingresso alla memoria bisogna
inviare EX/MEM.B oppure qualcosaltro se questo destinazione di un operazione precedente. Quindi in
ingresso alla memoria dati ci sar un Mux con una logica come quella appena descitta.
Ricapitolando, nei conflitti di dato, quindi attraverso degli accorgimenti sullunit di controllo, come la corto
circuitazione dellALU, si possono evitare stalli nel caso di conflitti tra dati con operazioni ALU. Invece nel
momento in cui listruzione generica fa riferimento a un sorgente che destinazione di un istruzione LOAD,
si costretti in questo caso a pagare uno stallo.

77

LOAD R6, 128(R0)


ADD R8,R6,R7
Durante listruzione ADD, R6 non disponibile in nessun punto del processore in maniera tale che
listruzione ADD possa disporre di questo valore un attimo prima della fase EX. In LMD c il valore di R6
solo al termine della fase MEM della LOAD, che corrisponde alla fase EX della ADD, e invece R6 si necessita
allinizio di questa fase EX.
In casi analoghi, come abbiamo visto, dirottiamo EX/MEM.ALUOutput oppure MEM/WB.ALUOutput
allingresso dellALU. In questo caso invece non c niente da fare, lunica soluzione eseguire listruzione
LOAD con tutte le sue fasi, e listruzione successiva eseguir la fetch e la decode e poich la fase di EX non
potr avere ancora il valore di R6 lunit di controllo staller lesecuzione di questa, che riprender
successivamente una volta ottenuto il dato in LMD.
Anche listruzione successiva alla ADD dovr stallarsi per evitare conflitti strutturali. Altrimenti verr
utilizzata lALU da entrambe le istruzioni. Cos anche le successive istruzioni per lo stesso motivo dovranno
stallarsi anchesse.
In conclusione viene perso un colpo di clock. Un programma di quattro istruzioni cos fatto invece di
terminare dopo 8 colpo di clock, (N-1) + 5, terminer dopo 9 colpi di clock, (N-1) +5+ n. stalli per istruzione,
nel nostro caso 1.
Si pu fare qualcosa per non perdere questo colpo di clock ed evitare lo stallo? Dipende. Dipende
dallistruzione che c dopo listruzione ADD. Ipotizziamo di avere questa situazione
LOAD R6, 128(R0)
ADD R7,R6,R2
MULT R1,R2,R3
Il compilatore potrebbe scambiare le due istruzioni ADD e MULT:
LOAD R6, 128(R0)
MULT R1,R2,R3
ADD R7,R6,R2
Dopo la LOAD conviene eseguire listruzione MULT che non utilizza R6. Quando opera la ADD, la EX si trova
al termine della MEM della LOAD e potr quindi utilizzare LMD per ottenere R6 senza stallarsi questa volta.
Questa operazione si chiama schedulazione statica delle istruzioni. Schedulando una istruzione fra la LOAD
e listruzione che usa il destinazione della LOAD non c pi bisogno di stallare. Se fosse stato:
LOAD R6, 128(R0)
ADD R7,R6,R2
MULT R1,R7,R3

78

La mult utilizza questa volta R7. Scambiando le due istruzioni, ADD e MULT come prima, nella MULT viene
usato R7 che non la somma di R6 e R2, un valore errato. In questo caso la schedulazione vista prima non
effettuabile.
Il compilatore, dunque, normalmente compila il programma traducendo il codice nel linguaggio di pi alto
livello, come il C ad esempio, in linguaggio macchina. Dopo esegue delle passate di ottimizzazione. Si
accorge che il programma dovr stallare e controlla se questo stallo si pu evitare inserendo un'altra
istruzione. Cerca un istruzione da mettere in mezzo alle due senza sconvolgere il programma. In generale
nessun sorgente di questa istruzione tappa buchi deve avere come sorgente una destinazione di una
istruzione che viene scavalcata con la schedulazione. Oppure se necessario farla, bisogna utilizzare un
registro differente, bisogna modificare i registri. Un modo quindi di ottimizzare le prestazione limitando gli
stalli avviene a livello di schedulazione. Si mettono le istruzioni al posto dello stallo. Se si riesce si ritorna
nella condizione ottimale.
Si potrebbe pensare, allora, che con questo stratagemma si potrebbe evitare la complicazione dellunit
di controllo, evitando cio la corto circuitazione dellALU. Se cos fosse gli stalli aumenterebbero. Se non ci
fosse la corto circuitazione bisognerebbe aspettare ogni volta che i dati delle istruzioni precedenti vengano
scritti , lID di una istruzione andrebbe eseguita solo dopo la WB della precedente. Verrebbero aggiunti tre
stalli. Il compilatore per evitare tutto questo dovrebbe trovare tre istruzioni da scambiare, il quale diventa
molto pi complicato. La corto circuitazione riduce il numero degli stalli e il numero di istruzioni da
rimpiazzare che lo schedulatore dovrebbe trovare.
2. Conflitti sulle Diramazioni
Entra in gioco il problema legato al flusso delle istruzioni. Come abbiamo visto le istruzioni vengono
eseguite ad ogni colpo di clock. Il problema che si presenta che bisogna conoscere lindirizzo
dellistruzione da prelevare. Affinch si possa prelevare ad ogni colpo di clock una istruzione, bisogna
conoscere ogni volta lindirizzo di questa. Per poter prelevare un istruzione necessario che
laggiornamento del PC avvenga nella fase di fetch di una istruzione. Il PC, appena spedito alla porta indirizzi
viene aggiornato, PC+4, in maniera tale che al prossimo colpo di clock rispendendolo nuovamente alla
memoria indirizzi, venga prelevata listruzione successiva. Questo corretto poich siamo nella situazione
che listruzione da prelevare subito dopo questa, le istruzioni sono scritte una dopo lalta, e inoltre sono
di lunghezza fissa.
Queste operazioni per non risolve il problema che si presenta quando listruzione di salto. Quando ci si
trova di fronte ad una istruzione di salto, condizionato o incondizionato, la prossima istruzione non a
PC+4, ma a un altro indirizzo.
Se questa istruzione di salto, al prossimo colpo di clock non si pu prelevare listruzione da eseguire?
Dipende. Si pu fare se nella fase di fetch si gi calcolato lindirizzo dove saltare, e inoltre si calcolato se
il salto si deve fare oppure no. Per si conosce se listruzione di salto o meno soltanto al termine della
fase di decode (sembra che un cubo di Rubik dove hanno staccato i pezzi dei colori e gli hanno messi in
modo che non lo si pu pi risolvere!). A livello deterministico non si pu garantire niente, si possono fare
per delle speculazioni. Attraverso dei meccanismi si pu prevedere se questa un istruzione di salto, si
potrebbe scommettere anche s il salto si verifica o meno, e anche dove saltare. In fase di esecuzioni si
attivano meccanismi speculativi, che non garantiscono se davvero la cosa giusta da fare. Si possono fare

79

delle previsioni dinamiche in fase di esecuzione di quello che potrebbe succedere, saltare a una istruzione o
meno. Questo discorso verr analizzato successivamente nel corso dei capitoli.
Quindi dopo aver prelevato listruzione, e se questa di salto non possibile saperlo ancora prima del
termine della fase di decode, normalmente sar prelevata listruzione successiva, mentre la prima quindi
in fase di esecuzione. Effettivamente si pu prelevare listruzione dove saltare soltanto una volta
determinato lindirizzo dove saltare e dopo aver terminato se la condizione stata verificata o meno.
Questo avviene al termine della fase di EX. Nella fase di MEM, invece, ALUOutput va in PC al posto di NPC.
Al termine della fase MEM in PC c il valore corretto. Soltanto ora il fetch si trover ad utilizzare il vero
indirizzo dellistruzione. Intanto per sono state eseguite delle istruzione. E queste istruzioni che sono state
avviate fintanto che non si calcolato lindirizzo di salto, hanno fatto lavoro inutile. Queste istruzioni, per,
non hanno fatto danno. Si pu pensare che queste istruzioni modifichino dei registri, ma i registri vengono
scritti soltanto nella fase di WB. Fino alla fase di WB di questa, listruzione di salto gi arrivata nella fase di
EX e in qualche modo si deciso se saltare o no. Lunit di controllo appena verificato se saltare o meno, se
il salto si deve effettuare abortisce le istruzioni avviate dopo di lei. Le istruzioni STORE scrivono in memoria
durante la fase di MEM, la EX dellistruzione di salto sar stata eseguita e avr quindi abortito questa. A
seconda di come gestire il meccanismo di calcolo e di decisione se saltare o meno, ci sono un certo numero
di istruzione che sono partite dopo listruzione di salto nel frattempo che si deciso cosa fare. Questo
numero di istruzioni prende il nome di penalit di salto. Ci sono diverse soluzioni per gestire queste
penalit di salto.
1)

Queste istruzioni potrebbero non essere eseguite, cio fermare per un certo numero di clock il
sistema finch non viene calcolato lindirizzo e viene verificata la condizione per saltare. (Reginella
quanti passi devo fare per arrivare al tuo castello con la fede e con lanello?!) In base al numero di
colpi di clock necessari, ad esempio n colpi di clock, si ha una penalit di salto di n colpi di clock. Si
cerca di ridurre questa penalit e anticipare al prima possibile il calcolo dellindirizzo dove saltare e
la decisione se il salto si verifica o meno.
2) Una soluzione pi intelligente sarebbe quella, come abbiamo detto prima, di continuare
lesecuzione delle istruzione, a patto che nel momento in cui si deciso di saltare vengano bloccate
queste istruzioni. Il processore, perch questa fase di calcolo dellindirizzo e decisione dipende dal
tipo di processore, mi deve garantire che si in tempo ad abortirle queste istruzioni speculative
prima che queste facciano danni. Con un struttura appena descritta in cui si decide se saltare o
meno prima della fase MEM possiamo utilizzare questa soluzione. Consapevoli che se listruzione
di salto vengono bloccate le istruzioni. Limportante quindi che non siano stati scritti registri, e
non sia stata scritta la memoria. Se listruzione di salto invece non viene verificata non si perso
tempo, non si verifica non si paga penalit. Quando il salto si verifica, queste istruzioni vengono
abortite in tempo

80

3) Un ulteriore soluzione potrebbe essere quella mettere


le istruzioni precedenti al salto dopo e non abortire
pi queste. Supponiamo di avere una penalit di salto
pari a n istruzioni, che eventualmente potrebbero
essere bloccate se il salto si verifica. Dopo listruzione
di salto vengono avviate n istruzioni in tanto che si
deciso se saltare o no. Il compilatore pu prendere n
istruzioni precedenti allistruzione di salto, che devono
essere comunque eseguite, e che siano indipendenti
dallistruzione di salto, e metterle dopo le istruzioni di
salto. Listruzione di salto ora diversa, avr un codice
operativo diverso, perch a prescindere se saltare o meno le istruzioni avviate dopo non devono
essere abortite, perch sono istruzioni che vanno comunque eseguite. Una volta calcolato il nuovo
PC e aver calcolato la condizione, le istruzioni dentro la pipeline vanno portate a termine, perch
non sono istruzioni, dal punto di vista logico, dopo listruzione di salto e quindi da bannare se il
salto si verifica, ma sono istruzioni che andrebbero comunque eseguite. Invece di avere una
penalit di salto a rischio aborto quelle istruzioni vengono portate a termine. Il compilatore deve
trovare un numero di istruzioni da eseguire che non siano dipendenti dallistruzione di salto. Ad
esempio se listruzione di salto , salta quando R1 != 0, e ci sono istruzioni precedenti che lavorano
su R1, queste non possono essere spostate. Si codifica listruzione di salto con un codice operativo
diverso a indicare che le istruzioni dopo non sono speculative. Se invece non vengono trovate
queste istruzioni, le istruzioni vengono eseguite in maniera speculativa e se non si effettua il salto
queste verranno bloccate perch erano quelle successive al salto dal punto di vista logico. Se il
compilatore trova n istruzioni, dove n la penalit di salto, le sposta dopo il salto. Il codice
operativo dellistruzione di salto cambia, perch le istruzioni dopo vengano portate a termine. Se
queste istruzioni non vengono trovate, verranno eseguite le istruzioni dopo speculative.
Listruzione di salto ha un altro codice operativo che bloccher quando bisogner saltare.
4) Analizziamo un'altra soluzione utile da adottare quando il compilatore non trova istruzioni da
mettere dopo il salto, e questa istruzione salta nel 90% dei casi. Cio nel 90% dei casi queste
istruzioni speculative verranno abortite, verranno pagate nel 90% n penalit di salto: 0.9*n.
Come si migliora questa situazione? Ipotizziamo di avere una penalit di salto pari a 3, e nella
situazione descritta il compilatore non ha trovato 3 istruzioni da inserire dopo il salto da eseguire
comunque. Il compilatore sposta le 3 istruzioni che sono allindirizzo di salto subito dopo
listruzione di salto. La quarta istruzione che cera allindirizzo di salto diventer ora lindirizzo di
salto. Iniziata listruzione di salto verranno comunque eseguite le
successive, quelle che sono state spostate, che sono
destinazione del salto. Listruzione di salto, con un codice
operativo differente anche questa volta, operer in questo
modo: se bisogna saltare le istruzioni che avviate verranno
portate a termine, se invece non bisogna saltare queste
istruzioni avviate verranno abortite. Se listruzione di salto da
fare, in automatico sono state fatte quelle istruzioni e poi si
andr allindirizzo di salto. Se invece il salto non si deve fare
quelle non dovevano essere eseguite e allora verranno abortite.
Il ragionamento inverso al secondo caso analizzato. Come si fa a capire se il salto avviene al 90%
81

dei casi? Se quella un istruzione di salto al termine di un loop, il compilatore sa che il loop il pi
delle volte salter e quindi il pi delle volte avverr il salto.
Si pu avere una situazione di questo genere con il salto indietro piuttosto che in avanti. Le
istruzioni destinazione del salto questa volta verranno replicate e non spostate come prima.
Se il salto pi facile che non si verifichi, si lasciano le cose come stanno, e se il salto si verifica
verranno abortite le istruzioni. Se il salto, invece, pi facile che si verifichi, verranno prelevate le n
istruzioni di penalit dalla destinazione del salto e si andranno a mettere subito dopo listruzione di
salto, e se il salto non si verifica verranno abortite le istruzioni avviate, altrimenti verranno eseguite
e si andr allindirizzo di salto. Se il salto indietro questa una copia e non uno spostamento.
La situazione cos schematizzata:

Riguardo la schedulazione del codice nel momento in cui si ha un istruzione di salto lidea quella di
inserire nel ritardo di salto che viene chiamato DELAY SLOT, unistruzione che deve essere comunque
eseguita. Nel caso a) la somma di R2 ed R3 in R1 pu essere tranquillamente messa al di sotto del test.
Questo spostamento non poteva essere effettuato qualora la somma aggiornasse il valore di R2, perch
altrimenti si sarebbe effettuato un test con un valore di R2 errato. Con la situazione cos descritta non si
avranno problemi quindi sia se il salto dovr essere effettuato e sia se il salto non dovr svolgersi. Il codice
operativo che codificher questo branch sar un codice operativo che avviser il sistema (lunit di
controllo) che nel momento in cui questa istruzione avr verificato che il salto si dovr effettuare,
listruzione avviata speculativamente deve comunque essere portata a termine. Se invece lunit di
controllo si accorge che il salto non va effettuato, listruzione avviata speculativamente verr abortita. Nel
caso b) invece nel delay slot in sostanza, si inserisce listruzione che andrebbe eseguita qualora non bisogna
saltare, e quando il salto dovr essere effettuato, lunit di controllo abortir questa istruzione. Questo

82

branch sar codificato con un ulteriore codice operativo. Quindi ci sono tre codici operativi diversi per
codificare un istruzione di tipo branch.
Come visto nei capitoli precedenti, durante la fase EXE avviene il calcolo di un risultato oppure il calcolo di
un indirizzo di salto oppure il calcolo di un indirizzo di memoria.

Per quanto riguarda un istruzione di salto oltre alla parte ALU, per il calcolo del nuovo indirizzo, si aggiunge
questa logica che verifica se il salto va fatto oppure no, andando a sovrascrivere nel latch Cond un valore,
che potrebbe essere per esempio 1, se il salto va eseguito, 0 altrimenti. Questo latch pilota luscita del Mux
in alto. Se Cond vale 1, il Mux piloter in uscita il valore proveniente da ALUoutput, cio NPC +Immediato,
altrimenti NPC; NPC che in sostanza vale PC+4 cio punta allistruzione successiva a quella corrente. Quindi
quando viene decodificata un istruzione di branch , al prossimo colpo di clock, lunit di controllo far
passare NPC sommato allimmediato che da 16 bit. Se invece listruzione decodificata una jump passer
limmediato da 26 bit invece, perch listruzione di tipo jump costituita, a parte i bit per il codice
operativo, da 26 bit per limmediato. In sostanza nella fase di EXE di questa istruzione si decider se saltare
o meno. Durante questa fase di EXE si effettuato il fetch di altre due istruzione che verranno avviate
quindi speculativamente, e solo dopo queste due verr fatto il fetch dellistruzione corretta, perch solo
quando questa fase terminata effettivamente si conoscer lindirizzo a cui saltare. Per cui il delta slot con
questa architettura in sostanza di 2 istruzioni.

83

La figure seguente mostra invece larchitettura del processore pipeline.

Il test viene eseguito su ID/EX.A, che contiene il valore di un certo registro. Se questo che maggiore o
minore di 0 si decide se saltare o meno e si setta il latch Cond che in questa architettura EX/MEM.Cond il
quale controlla il mux. Quindi la situazione la seguente: ad un certo colpo di clock entra nella fase di IF un
istruzione di salto che al prossimo colpo di clock verr decodificata e al successivo colpo di clock verr
calcolato lindirizzo di salto e in sostanza c un delay slot di 2 istruzioni. Solo dopo che listruzione di salto
ha terminato la fase di EXE , cio quando entra nella fase di MEM, c uninstruction fetch garantito.
Intanto avvenuto il fetch e lesecuzione di due istruzioni che rappresentano uno stallo oppure una
speculazione, oppure delle normali esecuzioni in quanto il compilatore riuscito a trovare due istruzioni da
inserire che andrebbero eseguite a prescindere dal risultato del test. In questultimo caso il compilatore
deve trovare due istruzioni che non abbiano dipendenze con il test e che possono essere spostate dopo la
condizione. Questo pu rappresentare un problema perch non detto che il processore riesca a trovare
delle istruzione di inserire nel delay slot, e quindi si otterrebbe uno stallo oppure una esecuzione di
istruzioni speculative. Si cerca allora di ridurre questo numero di istruzioni, diminuire il delay slot di 2
istruzioni.
Si pu scommettere sul branch, cio si inseriscono delle istruzioni perch si scommette che il branch non
verr eseguito per la maggior parte delle volte. Questo comporta che se la previsione errata vengono
avviate 2 istruzioni che verranno abortite per la maggior parte delle volte. Bisogna valutare quanto bravo
il predittore (branch prediction), ovvero un modulo dellunit di controllo che fa delle predizioni, una sorta
84

di book-maker! Se il predittore sbaglia tante volte , tante volte verranno pagati n stalli, dove n in generale
il numero di istruzioni da inserire nel delay slot.
E possibile diminuire il delay slot al valore0? Un delay slot pari a 0 significa che ogni volta si esegue un
fetch di un istruzione certo, cio il calcolo dellindirizzo di salto andrebbe fatto nella fase di IF dellistruzione
di salto, ma questo non possibile poich sta ancora avvenendo il prelievo dellistruzione, e inoltre non si
conosce listruzione non si sa se di salto o meno. Non possibile avere un delay slot uguale a 0.
Vediamo se possibile determinare lindirizzo di salto nella fase di ID dellistruzione di salto e quindi avere
un delay slot uguale a una sola istruzione. Questo significa che il compilatore dovr trovare una sola
istruzione, che deve comunque essere eseguita, dopo questa istruzione di salto. Quando questa istruzione
non trovata, perch non esiste nel codice o anche perch il compilatore non la sa individuare, se il branch
prediction fallir, si avr un aborto di una sola istruzione invece che 2 come abbiamo visto prima: ridurre a
uno la penalit di salto, il delay slot. Ridurre a una istruzione questo delay slot significa in sostanza calcolare
lindirizzo di salto nella fase di ID. Attenzione per che nella fase di ID sta avvenendo la decodifica
dellistruzione , cio non si conosce se listruzione corrente di salto o meno, e quindi a cosa serve
calcolare lindirizzo di salto? Si calcola lindirizzo di salto nella fase di ID in maniera speculativa, come il
resto delle altre operazioni, si calcola lindirizzo comunque mentre si cerca di capire se quella un
istruzione di salto . Questo indirizzo di salto viene posteggiato da qualche parte e alla fine di questa fase di
ID, quando si deciso se listruzione di salto, quellindirizzo calcolato verr scritto nel PC, altrimenti, se
listruzione decodificata non un istruzione di salto, lindirizzo rimarr inutilizzato. Bisogna per anche
capire se il salto si verifica o meno, valutare la condizione di branch. Quindi bisogna anticipare nella fase di
ID sia il calcolo dellindirizzo e sia la valutazione della condizione . Per la valutazione della condizione si
inserisce il modulo Zero? nella fase di ID, mentre per il calcolo dellindirizzo di salto non si pu utilizzare
lALU, perch durante la fase di ID lALU utilizzata dalla fase di EXE dellistruzione precedente, quindi
necessario utilizzare un sommatore, che esegue la somma tra limmediato e NPC.
Larchitettura del processore diventa la seguente:

85

Come si nota il modulo per la verifica del test spostato nella fase ID, per stato necessario pagare un
sommatore in pi. Per cui il modulo Zero? decide se il salto va eseguito o meno e se necessario il risultato
viene congelato nel caso listruzione si scopre non essere di salto. A questo punto si certi che al colpo di
clock successivo a quello in cui listruzione di salto ha eseguito lID , lIF della nuova istruzione corretta.
Prelevata listruzione a un certo colpo di clock, al colpo di clock successivo questa passa alla fase di ID, e
viene prelevata speculativamente una certa istruzione. Al prossimo colpo di clock, decodificata listruzione
di salto viene prelevata listruzione corretta. La figura un po carente perch non mostra cosa accade nel
caso di una istruzione jump, per in realt un jump non ha bisogno di verificare la condizione, e lindirizzo
viene calcolato dallimmediato da 26 bit.

86

Esercizi
Esercizio 1.
Scrivere un codice in Assembler che calcoli il valore massimo di una matrice double, con n righe e m
colonne, caricata in memoria per righe.
Soluzione:

R1 = n, R2 = m,
R3 = M[0][0]

R5 <- 0

R4 <- R3
R6 <- R1*R2

F
R5 < R6

R3 <- R3+8

R7<- M[R3]

V
F
R4<R7

R4<- R7

R4

87

Codice non ottimizzato:


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

DADDI R5,0(R0)
LD R4,0(R3)
DMUL R6,R1,R2
SLT R8,R5,R6
BEQZ R8,6
DADDI R3, 8(R3)
LD R7,0(R3)
SLT R8,R4,R7
BEQZ R8,1
DADDI R4,0(R7)
DADD R5,1(R5)
J -8

Un' algoritmo diverso (non ottimizzato):


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

LD R4,(R3)0
DMUL R5,R1,R2
DSLL R5,(R5)3
DADDI R6,(R0)8
DADD R10,R6,R3
LD R7,(R10)0
SLT R8,R4,R7
BEQZ R8, 1
DADD R4,R0,R7
DADDI R6,(R6)8
SLT R8,R6,R5
BEQZ R8,-8

Su questultimo codice si possono eseguire diverse modifiche o effettuare nuove varianti. Una variante
potrebbe essere quella di percorrere il vettore dal basso verso lalto evitando quindi alcune istruzioni e
riducendo il numero di colpi di clock necessari allesecuzione. Questultimo codice impiega 4+n*m*8 colpi
di clock.

88

Esercizio 2.
Scrivere un codice Assembler che legga un vettore double, di dimensione n, a partire da un indirizzo, e
inserisca in un altro vettore, a partire da un altro indirizzo, gli elementi del primo moltiplicati per 2.
Soluzione:

R1 = V[0], R2 = n,
R3 = W[0]

R5 <- R2

R5 <- R5*8

R5 <- R5 -8

R6 <- R5+R1

R4 <- M[R6]

R4 <- R4*2

R6 <- R5 +R3

M[R6] <- R4

F
R5 == 0

END

89

Codice non ottimizzato:


1.
2.
3.
4.
5.
6.
7.
8.

DSLL R5, (R2)3


DADDI R5,(R5)-8
DADD R6,R5,R1
LD R4,(R6)0
DSLL R4,(R4)1
DADD R6,R5,R3
SD R4,(R6)0
BNEZ R5, -7

Questo codice impiega 2+n*7 colpi di clock.


Il codice ottimizzato:
1.
2.
3.
4.
5.
6.
7.
8.

DSLL R5, (R2)3


DADDI R5,(R5)-8
DADD R6,R5,R1
LD R4,(R6)0
DADD R6,R5,R3
DSLL R4,(R4)1
BNEZ R5, -7
SD R4,(R6)0

Questo codice invece impiega 2+5*n+1 colpi di clock

90

Esercizio 3.
Scrivere un codice Assembler in cui dati due vettori, di dimensione n, a partire da due indirizzi di memoria,
scrivi un ulteriore vettore i cui elementi sono dati dalla somma degli elementi dei primi due vettori: X =
V+W.

Soluzione:

R1 = V[0], R2 = n, R3 =
X[0], R4 = W[0]

R5<- R2
R5<- R5*8

R5 <- R5-8

R6 <- R5+R4

R8<- M[R6]

R6 <- R5+R1

R7<- M[R6]

R8<- R8+R7

R6 <- R5+R3

M[R6] <- R8

R5 ==0
V
END

91

Un codice ottimizzato potrebbe essere:


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

DSLL R5,(R2)3
DADDI R5,(R5)-8
DADD R6,R5,R4
LD R8,(R6)0
DADD R6,R5,R1
LD R7,(R6)0
DADD R6,R5,R3
DADD R8,R7,R8
BNEZ R5,-8
SD R8,(R6)0

Un ulteriore codice:
1.
DADDI R4, (R0)0
2.
DSLL R9,R5,3
3. ciclo: DADD R7,R2,R4
4.
DADD R8,R3,R4
5.
LD R12,(R7)0
6.
LD R13,(R8)0
7.
DADD R6,R1,R4
8.
DADD R12,R12,R13
9.
DADDI R4,R4,8
10.
BNE R4,R9,ciclo
11.
SD R12,(R6)0

92

Esercizio 4.
Analizzare le fasi dellistruzione COPY, cos definita:
COPY Rs1 ,Rs2 ,Imm

M[Rs1+Imm] ->M[Rs2]

IF
IF/ID.IR < M[PC];
IF/ID.NPC, PC <- if((EX/MEM.opcode == branch) & EX/MEM.cond)
EX/MEM.ALUoutput;
Else
PC+4;
ID
ID/EX.A <- Regs[IF/ID.IR[rs]];
ID/EX.B <- Regs[IF/ID.IR[rt]];
ID/EX.NPC <- IF/ID.NPC;
ID/EX.IR<- IF/ID.IR;
ID/EX.Imm <- sing-extend[IF/ID.IR[immediate field]];
EX
EX/MEM.IR<- ID/EX.IR;
EX/MEM.ALUoutput <- ID/EX.A + ID/EX.Imm;
EX/MEM.B <- ID/EX.B;
MEM1
MEM/WB.IR <- EX/MEM.IR;
MEM/WB.LMD <- M[EX/MEM.ALUoutput];
MEM/WB.B <- EX/MEM.B
MEM2
M[MEM/WB.B] <- MEM/WB.LMD;

93

94

CAPITOLO 10

IL COSTO DI UN CIRCUITO INTEGRATO


Ora facciamo un breve cenno ad alcuni fattori che determinano il costo del circuito integrato. Alcuni di
questi fattori usufruiscono di un abbattimento quando poi nella produzione si va a produrre un quantitativo
piuttosto che un altro, viceversa altri fattori non subiscono questo abbattimento.
1. La progettazione
Il primo fattore da considerare quello legato alla progettazione, questo fortunatamente viene abbattuto
quando si produce un quantitativo di pezzi maggiore rispetto a uno minore. Se si spende una certa somma
per progettare un circuito elettronico, questa dovuta allo stipendio per il numero di ingegneri che devono
lavorare al progetto per un certo numero di settimane o di mesi , e una parte allammortamento delle
macchine su cui questi ingegneri lavorano. L'ammortamento il piano di restituzione graduale di un debito
mediante il pagamento periodico di rate. Quello che viene da questa somma di costi, che essenzialmente
lo stipendio che si paga ai cervelli che lavorano su un sistema, va aggiunto il costo della struttura (ufficio,
capannone). Una volta che il progetto terminato, questi costi non aumentano se decido di produrre un
chip o produrne dieci, per cui se il progetto costa 100.000 chiaro che vendendo 1.000 chip, da ciascuno
di questi bisogna ricavare almeno 100 per rifarsi almeno delle spese di progetto. Vendendo un solo chip,
questo dovr costare 100.000 , altrimenti c un problema nel business plan. Il progetto, prima di arrivare
alla produzione subisce delle verifiche. Si fanno delle simulazioni e dei test. Per esempio un modo di testare
un circuito integrato, a livello funzionale, quello di generare dei files di stimolo (degli ingressi) e vedere
sul simulatore in virt di quegli ingressi come si comporterebbe quel circuito integrato. Se il circuito
integrato deve eseguire operazioni che sono relativamente piuttosto semplici, il test anche veloce.
Per rete combinatoria intendiamo un circuito con degli ingressi e delle uscite che sono funzioni degli
ingressi. Applicando degli ingressi, questo oggetto deve produrre delle uscite che sono funzioni di questi
ingressi. Per esistono circuiti un po pi complicati che prendono il nome di macchine a stati, in cui per
determinare luscita non basta conoscere lingresso, perch per dire quellingresso quale uscita produce
bisogna conoscere non solo lingresso ma anche lo STATO precedente del circuito. Un banale esempio di
macchina a stati il contatore, il quale riceve un impulso e conta limpulso. Quando nellingresso
presente limpulso non si conosce luscita. La funzione che regola il valore delluscita una funzione che
ovviamente dipende dallingresso ma anche dallo stato del sistema e cos come a ogni ingresso cambia
luscita , c anche un'altra funzione che va a modificare lo stato. Quindi luscita allistante t dipende
dallingresso allistante t e dallo stato in t-1; e lo stato nellistante t dipender dallingresso t e dallo stato
allistante t-1. Quindi c una variabile in uscita che quella osservabile e che interessa; ma per descrivere il
funzionamento di questa macchina c anche un'altra variabile chi si chiama STATO, e che anchessa cambia
di volta in volta ed lei insieme allingresso a determinare il valore di uscita. Quindi oggetti di questo
genere sono molto pi complessi da testare.
Le cose possono essere ancora pi complicate se parlo di sistemi che hanno la memoria pi lunga, luscita
dipende sia dallo stato allistante precedente ma anche da stati di istanti ancora precedenti. Bisogna sapere
cosa cera prima e sapere anche cosa cera prima ancora. Quindi le combinazioni possibili aumentano.

95

2. La produzione
Nella produzione ci sono dei costi che dipendono anche dal tipo di cliente. Non tutti quelli che producono
circuiti integrati effettivamente producono loro il circuito integrato. Ci sono nel mondo poche fonderie.
Il circuito integrato si costruisce sul wafer costituito da germanio o silicio, possibilmente privo di impurit,
opportunamente drogato.

In un crogiuolo ad alte temperature si fonde il silicio(o germanio) e si procede con la fase di drogaggio: un
operazione chimica che aggiunge e toglie elettroni agli atomi di silicio e con questa operazione quella
materia si riesce a comportare come un transistor piuttosto che come un condensatore ecc. Quello che
interessa avere un idea del perch un chip che contiene 1000 transistor costi di pi di un chip che ne
contiene 10, dove ovviamente questi numeri sono del tutto casuali perch in un chip ci sono migliaia di
transistor. Questo wafer ha una forma circolare e non quadrata perch quando si prepara un wafer, il
processo di fabbricazione tale da generare una forma circolare perch questo materiale si espande
radialmente. Allora al termine quando il processo di fabbricazione di questo materiale ha prodotto questa
fetta, essa ha una forma circolare. Su questo materiale bisogna inserire i package (contenitore in cui sono
racchiuse alcune tipologie di componenti elettronici: circuiti integrati ecc..), che possono avere una
dimensione variabile e forma rettangolare o quadrata. Si disegna in questa geometria circolare un certo
numero di pezzi i quali sono tali da essere replicati ma ovviamente con alcuni di questi che verranno buttati
perch mancheranno delle parti. Se con una certa area di wafer si riescono a costruire N chip, non detto
che con un area di wafer doppia si riescano a inserire 2N chip. Questa proporzione si manterrebbe se la
geometria non fosse tonda. Interviene un fattore tragico che spinge a pensare in fase di progetto come
ridurre il numero di transistor e quindi larea di un singolo chip. I chip sani che si ottengono dopo che sono
stati inseriti sul wafer non detto che siano pronti per essere venduti, perch questi oggetti possono
essere difettosi. Le impurit presenti sul wafer determinano il fatto che quei chip costruirti su di un area
impura del wafer vadano scartati. Il numero di chip da buttare dipende anche dallarea del singolo chip.
Supponiamo che limpurit abbia una probabilit di esistere, per esempio , minore dello 0,001 % in 1 cm 2 , a
96

significare che in 1 cm2 di questa materia ci sono delle impurit con questa frequenza. Questo significa che
se c l 1% di impurit e ci sono 100 pezzi da 1 cm2 , significa che un pezzo mediamente verr gettato. La
resa di produzione del 99 %. Se viene considerato un chip che non funziona, e viene venduta una
motherboard con questo chip difettato, ritorna indietro tutta la motherboard!
Il test ha un costo che vale per ogni pezzo , non per ogni pezzo che viene vendo, ma anche per quelli che
vengono venduti va fatto. Ci sono dei pacchetti software la cui uscita il codice per controllare il forno di
drogaggio di questi oggetti. Attraverso il codice VHDL (linguaggio che serve a descrivere il funzionamento di
un circuito elettronico) il software fornisce dei files per configurare il drogaggio del circuito elettronico
stesso. Anche questo test non sar esaustivo, per in media va ad esplorare la casistica pi disparata degli
stati in cui quel sistema pu venirsi a trovare.
A corredo di queste tecniche ci sono delle filosofie di FAULT TOLERANCE , propriet di garantire a quel
circuito una tolleranza ai guasti. Quindi per esempio si possono creare delle ridondanze in maniera tale che
nel momento in cui in un certo settaggio di quel circuito non funziona bene perch per esempio c un
impurit, e questa danneggia per esempio un sommatore, se quel sommatore stato replicato nel package,
si configura quel chip a usare laltro sommatore e quindi a risolvere questo problema.

97

98

CAPITOLO 11

IL PROCESSORE FLOATING POINT

Tutto ci che stato mostrato finora relativo al processore in virgola fissa, cio a significare che gli
operandi su cui esso opera sono operandi codificati in binario complemento a 2 e non in floating point. Le
operazioni binarie in complemento a 2 hanno una latenza che pi o meno riconducibile alla latenza per
laccesso ad una memoria.
Ora faremo riferimento al processore che operi in hardware su dati a virgola mobile,
subroutine che eseguano algoritmi per il di calcolo fra dati in virgola mobile.

non utilizza

1. Operazioni Floating Point


Nello standard IEEE 754 i numeri reali in virgola mobile(floating point) vengono rappresentati nel seguente
modo:
considerando una rappresentazione a 32 bit(32bit singola precisione invece di 64 bit doppia precisione)

1 bit per il segno (1 bit per la doppia precisione)


8 bit per lesponente (11 bit)
23 bit per la mantissa (52 bit)

Aumentando i bit per la mantissa aumenta la precisione con cui rappresento un numero, aumentando i bit
per lesponente aumento la quantit di numeri che posso rappresentare. Per il bit del segno, il valore 0
indica un numero positivo, il valore 1 indica un numero negativo.
La mantissa(per gli informatici) il numero dopo la virgola che si ottiene spostando questa a destra
dellultima cifra diversa da 0, e lesponente pari al numero di volte che ho spostato la virgola, se verso
sinistra allora positivo altrimenti negativo. Inoltre il valore dellesponente va sommato al bias il cui valore
pari a 127 per la rappresentazione a 32 bit mentre 1023 per quella a 64 bit. Lesponente inoltre che pu
essere con segno o senza va scritto in complemento a 2 (CA2: devo complementare il numero, cio mettere
a 1 tutti gli zeri e a 0 tutti gli 1, e poi sommo 1).
Es.:
16,625 = 10000,101
La mantissa vale: 1,0000101 = 0000101
Mentre lesponente: 4(ho spostato la virgola di 4 posizioni verso sinistra)
4+127(bias) = 131 che in binario vale: 10000011
Il bit di segno vale 0 poich il numero positivo

99

Es.:
11011,11101
Questa volta ho a disposizione ad esempio 9 bit per la mantissa questa vale:
10111110
E come esponente -4 => -4+127 = 124 che in binario 1111011

1.1. Addizione
Nella somma di floating point di due numeri bisogna che i due numeri abbiano lo stesso esponente, in
particolare bisogna portare lesponente pi piccolo a quello del pi grande, facendo questa operazione
possibile che mi venga zero(quando un numero troppo pi grande di un altro la somma con questo mi da
un contributo trascurabile). Quindi scelgo lesponente maggiore poi prendo la mantissa del numero con
esponente minore e devo spostare la virgola: fare uno shift della mantissa.
Lo shift(scorrimento) lo spostamento dei valori dei bit di una posizione a sinistra o a destra in un registro
o in una posizione di memoria. Loperazione fa scorrere i bit uno dopo laltro verso destra(>>) o verso
sinistra(<<). Di solito si fanno scorrere i bit inserendo uno zero(a seconda delle posizioni) da destra o da
sinistra(a seconda di come sto shiftando), se devo fare uno shift di due posizioni si fanno entrare due zeri e
cos via. Lo shift rotatorio prevede che ci che esce da una parte entra dallaltra.
Es.:
111001011 << 2
100101100
10110101 << 2 (shift rotatorio)
01101011
Se il numero senza segno(binario puro) e faccio uno shift verso destra (>>) quello che entra uno zero, se
il numero in CA2 entra un bit uguale al primo bit che vedo prima dello shift.
Es.:
101001101 >> 1 (numero in CA2)
110100110

011001001 >> 2 (numero in CA2)


000110010

100

Dal punto di vista aritmetico loperazione di shift corrisponde a una moltiplicazione(shift verso sinistra <<)
x2^n (n numero di posizioni che ho shiftato) o a una divisione(shift verso destra >>) /2^n.
Il risultato della somma ha lesponente vincolato allesponente del numero maggiore. Prima di sommare le
mantisse devo normalizzare la mantissa allesponente del numero minore. Faccio la differenza tra gli
esponenti (esp. maggiore meno esp. minore) che ipotizziamo sia un valore m, e shifto verso destra di m
posti la mantissa pi piccola, ora sommo le mantisse.
Laddizione quindi consta di questi passaggi:

Differenza tra gli esponenti


Shift sulla mantissa con esponente minore
Somma delle mantisse

1.2. Moltiplicazione
Nella moltiplicazione di due numeri rappresentati in floating point, il nuovo numero ottenuto ha la
mantissa pari al prodotto delle mantisse e come esponente la somma degli esponenti. Bisogna stare attenti
a normalizzare la mantissa perch dalla moltiplicazione delle mantisse posso avere un risultato che
necessita di un numero di bit pi grande di quanti ne posso mettere nel campo mantissa, e devo inserire
allora i pi significativi(sposto la virgola) e i restanti li perdo. Ora nellesponente dovr considerare oltre alla
somma degli esponenti anche il numero di volte in cui ho spostato la virgola per inserire nella mantissa il
nuovo numero ottenuto dalla moltiplicazione.
Invece il segno del nuovo numero ottenuto dalla moltiplicazione dato dalloperazione di XOR dei due bit
di segno.
0 XOR 0 = 0 (+ x + = +)
1 XOR 1 = 0(- x - = +)
1 XOR 0 = 1(- x + = -)
0 XOR 1 = 1(+ x - = -)

2. Architettura Processore Floating Point


Dopo un breve cenno sul formato floating point e su come avvengono le operazioni su dati floating point
analizziamo larchitettura di questo processore. Come detto sopra per il calcolo della somma tra dati in
virgola mobile , bisogna fondamentalmente la somma delle mantisse, solo dopo aver confrontato gli
esponenti, e aver eseguito un eventuale shift della mantissa del numero meno significativo verso destra per
adeguare gli esponenti. Dopo la somma quindi si ottiene un numero floating point con la mantissa pari alla
una somma delle mantisse dei due addendi e lesponente diventa lesponente di quello pi significativo a
meno di un riaggiusto se nella somma delle mantisse si genera un carry.

101

Queste sono operazioni si possono realizzare con sommatori, comparatori, moltiplicatori in virgola fissa
trattando i campi del dato floating point come numeri in virgola fissa, per questo richiederebbe un po di
tempo. Un'altra soluzione quella di realizzare in hardware operazioni direttamente tra dati floating point,
e realizzando quindi su un circuito un moltiplicatore floating point oppure un sommatore floating point.
Per anche in questo caso la latenza di questi oggetti, del sommatore o del divisore o del moltiplicatore
floating point , sarebbe maggiore della latenza per esempio di un sommatore a virgola fissa oppure di un
unit che esegue lo shift di un registro oppure di un unit che opera lOR tra due registri e via dicendo. Cio
pur avendo realizzato in hardware dei moduli che eseguono il calcolo di operandi in virgola mobile, il tempo
necessario a processare questi dati pi ampio del tempo necessario a fare altro tipo di operazioni. Di
fronte a questo, il progettista pu decidere se allungare il colpo di clock in maniera tale da considerare il
colpo di clock sufficientemente ampio da permettere di eseguire loperazione pi lenta appena introdotta.
Quindi nel momento in cui bisogna eseguire una divisione floating point, la fase EXE richiede un po di
tempo maggiore rispetto a quello assegnato al colpo di clock finora. Si pu decidere di allungare il colpo di
clock. Supponendo che una divisione floating point duri 20 colpi di clock in pi rispetto alloperazione di
somma in virgola fissa, si dimensiona il colpo di clock a questo valore, la frequenza del clock va 20 volte pi
lenta, ciascuna fase viene eseguita 20 volte pi lentamente. Questo comporta che qualora nella fase EXE
c da eseguire unoperazione fra interi, questa fase si vedr correre 20 volte pi lentamente.
Si pu per fare in modo di unire pi fasi, facendo una under pipeline. Questo semplifica di parecchio
lunit di controllo perch essa dovr gestire la contemporanea esecuzione di soltanto 3 istruzioni, avendo
supposto che due fasi vengano unite in una. Se la pipeline diventata a 3 stadi, nel processore ci sono solo
3 istruzioni. Quindi dilatando il colpo di clock in maniera tale da contemplare il tempo necessario alla
operazione pi lenta, che adesso diventata per esempio la divisione tra due numeri floating point, viene
semplificata di molto lunit di controllo. In questo modo le prestazioni sono 20 volte pi basse. Attenzione
le prestazioni sono 20 volte pi basse anche qualora nel programma non capiter mai una divisione floating
point.

Allora pi che dilatare il colpo di clock , una soluzione pi attenta alle prestazioni, quella di considerare
una fase EXE un multi ciclo. Il colpo di clock resta inalterato, perch opportuno che il colpo di clock sia
dimensionato sul tempo utile per eseguire le fasi con operazione a virgola fissa. Questo colpo di clock,
come detto prima, troppo piccolo per eseguire per esempio una moltiplicazione floating point. Ebbene
102

listruzione che dovr eseguire una moltiplicazione floating point avr una fase EXE che durer un certo
numero di cicli. Quindi, alla fine, lidea non che decidendo di mantenere il colpo di clock basso come
periodo , comunque le operazioni floating point verranno effettuate pi velocemente, bens mantenere un
clock come quello considerato fino ad oggi che tuttavia impieghi pi battute affinch una fase EXE pi lenta
venga eseguita, visto che il problema nato dal fatto che per fare loperazione floating point si necessita di
pi tempo. Questa situazione non semplice da gestire. Significa ora che quando nella pipeline entra un
istruzione che una divisione fra due dati floating point, quellistruzione esegue le fasi ID e ID e poi
comincia la fase di EXE. Nel caso di una divisione floating point, questa fase terminer dopo 20 colpi di
clock, e poi verr eseguita la fase di MEM e poi la fase di write back. Al colpo di clock successivo parte un
istruzione che deve eseguire lo shift del registro R6 di 4 posti, per esempio; questa istruzione eseguir lIF ,
ID , EXE , MEM e WRITE BACK . Cio questa istruzione sar terminata mentre la prima istruzione, divisione
floating point, star ancora eseguendo il terzo ciclo di EXE. Si viene a creare un meccanismo che bisogna
essere capaci di controllare. Perch nel consentire questa durata diversa delle varie istruzioni , ci si immette
nel dover gestire la cos detta Terminazione delle istruzioni fuori posto , cio le istruzioni non termineranno
nellordine logico previsto dal programma, ma ci sar un istruzione, che nella logica del programma
dovrebbe terminare prima di un'altra, ma che effettivamente, necessitando di maggiore tempo, terminer
successivamente (conflitti di dato). Quando sono stati analizzati i conflitti di dato, considerando istruzioni di
uguale durata, tutte eseguivano la fase di write back, quindi andavano a scrivere il loro risultato nel banco
dei registri, prima delle istruzioni che venivano dopo di loro. Quindi nel caso ci sia un istruzione che scriva il
valore in R1 e la successiva anchessa scriva un valore in R1, non nascevano problemi in quanto lultima
istruzione esegue la fase di WB solo dopo la fase di WB della prima, e quindi il valore di R1 al termine delle
due istruzioni quello aggiornato dallultima istruzione. Ma con la situazione descritta prima delle
terminazioni fuori ordine delle istruzioni , si verificano dei problemi nel momento in cui una divisione deve
produrre un risultato, ad esempio F2 (per i floating point si utilizzano i registri F) e un istruzione magari pi
veloce, la somma ad esempio, produce F2 anchessa. Essendo la somma pi veloce termina prima e quindi
andrebbe a scrivere F2 prima della divisione che dal punto di vista logico dovrebbe scriverlo per prima.
Successivamente, terminata la divisione verr scritto il valore di F2 della divisione, e da quel momento in
poi in F2 non c il risultato dellistruzione ultima dal punto di vista logico del programma.
Esiste una casistica di conflitti di dato che ora diventa un po pi complessa.:
-

RAR (Read After Read)


RAW (Read After Write)
WAR (Write After Read)
WAW (Write After Write)

Il conflitto RAR, che in realt non un conflitto di dato, si genera quando un istruzione legge un registro, R1
per esempio, e un'altra istruzione legge anchessa R1, e la seconda istruzione pi veloce della prima e in
sostanza legge il valore di R1 per prima. Se questo succede non crea problemi, per cui questo in realt non
un conflitto. Questa situazione non neanche possibile che si verifichi, perch una istruzione legge il
registro nella fase di ID, per cui effettivamente nessuna istruzione che deve leggere dopo che la precedente
abbia letto andr a leggere prima un registro.
Invece nascono seri problemi quando una istruzione deve leggere un dato dopo che un istruzione
precedente lo abbia scritto, RAW. Data unistruzione che scrive in un registro, R1 per esempio, e un
istruzione successiva che deve leggere R1 per calcolare per esempio R2, naturalmente questultima
103

istruzione deve leggere R1 dopo che la prima lo abbia scritto altrimenti legge un valore errato di R1. Allora
un conflitto di dato si ha di tipo RAW nel momento in cui listruzione che deve leggere un operando dopo
che un istruzione precedente lo abbia calcolato o comunque determinato perch lo va a prelevare dalla
memoria, ebbene quellistruzione invece di leggerlo dopo lo legge prima. Questa come visto nei capitoli
precedenti si risolve, pensando al processore a virgola fissa, attraverso la cortocircuitazione dellALU o
stallando il processore in caso di accesso a memoria, cio se questo R1 destinazione di una LOAD.
WAR un conflitto che si genera nel momento in cui un deve scrivere in un registro dopo che un istruzione
precedente lo abbia letto lo stesso registro. Quindi se un istruzione scrive R1 prima che R1 sia stato letto
dalla precedente, e questa quindi legge R1 modificato. Questo problema in realt non esiste per come
stato strutturato il processore: la prima istruzione legger R1 nella fase di ID e la seconda scriver R1 nella
fase di WB. Ebbene la fase di write back parecchio successiva alla fase di ID di unistruzione precedente, in
realt quando listruzioni precedenti sta leggendo gli operandi , questa al pi presto sta eseguendo il fetch e
quindi non si verificher mai che un istruzione scriva un registro prima che la precedente abbia letto quel
registro. Quindi questo conflitto non esiste per come strutturata la pipeline.
Infine c il conflitto di tipo WAW: due istruzioni scrivono lo stesso registro e naturalmente una istruzione
che quella schedulata dopo scrive il registro prima di quella precedente. Al termine delle due istruzioni
quindi il valore del registro quello scritto dalla prima istruzione, la pi lenta, invece che dellultima, come
vuole la logica del programma. Nel processore a virgola fissa questo tipo di conflitto non esiste, perch un
istruzione scrive nella sua fase di write back mentre le precedenti istruzioni sono gi terminate e avranno
gi eseguito la loro fase di WB e quindi scritto nel banco dei registri.
Per cui nel processore a virgola fissa lunico tipo di conflitto che potrebbe generarsi il conflitto di tipo
RAW, risolto con la soluzione che illustrata nelle lezioni precedenti.
Con processori a virgola mobile avendo deciso di usare un clock non cadenzato sulla latenza dellunit
funzionale pi lenta, perch altrimenti verrebbero rallentate tutte le altre fasi, ma si continua a cadenzare
sullunit che era servita come definizione del clock fino a questo momento, per esempio la memoria, e si
introduce per le fasi pi lente un meccanismo di multi ciclo si generano questi conflitti. Allungare queste
istruzioni , in maniera non regolare, fa si che le fasi di write back di queste sono venute a generarsi, dal
punto di vista temporale, oltre le fasi di write back di istruzioni successive, che magari non erano state
dilatate. Questi conflitti ora possono esistere e bisogna preoccuparsi di risolverli.
In definitiva con un processore floating point, se pure alcune operazioni sono pi lente di quelle che viste
sino ad ora , si continua a mantenere il clock di un certo tipo e questo porter per a dilazionare con un
meccanismo di multi ciclo lesecuzione di alcune operazioni pi lente.
Un ulteriore considerazione che va fatta, quando abbiamo detto che alcune operazioni saranno pi lente,
quella di non raggruppare le unit di calcolo floating point in un unico modulo. LALU un modulo che
raggruppa al suo interno sommatore, comparatore, divisore e questo non crea problemi. Quando vengono
eseguite le istruzioni ad aritmetica fissa, ad esempio la somma tra due registri, non si impegna solo il
sommatore, ma si impegna lALU intera, visto che lALU raggruppa tutte le unit funzionali ad aritmetica
fissa. Inoltre al colpo di clock successivo, la prossima istruzione potr utilizzare lALU che sar disponibile
visto che la prima avr gi terminato e quindi sar stata liberata.

104

Non possibile ragionare in questi termini anche con lunit floating point decidendo ad esempio di
utilizzare ALU2 monolitica, ovvero lALU che contiene il moltiplicatore floating point, il sommatore floating
point , divisore floating point in un unico modulo gestita come lALU ad aritmetica intera. Questo
comporterebbe che appena un istruzione, ad esempio di moltiplicazione floating point, esegue la fase di
EXE, impegna ALU2 per 15 cicli; e quando al prossimo colpo di clock, nel programma, c una divisione
floating, questa vorr utilizzare ALU2 che ancora impegnata per loperazione di moltiplicazione. Quindi
listruzione di divisione floating point non solo dovr fare la divisione per 20 colpi di clock ma intanto dovr
attendere N colpi di clock affinch la moltiplicazione precedente liberi ALU2. Allora ovviamente non
conviene considerare lunit floating point organizzata in un unico modulo bens in diversi moduli separati
funzionalmente, in maniera tale che se si sta eseguendo una moltiplicazione e listruzione successiva una
somma floating point, si pu eseguire liberamente insieme a prodotto anche la somma. Quindi laltra cosa
da tenere a mente per un processore floating point che le unit di calcolo sono mantenute indipendenti.
Ci si potrebbe chiedere perch sono state raggruppate le unit dellALU se si potevamo tenere indipendenti
anche quelle? In realt raggruppandole la situazione pi facile da gestire, perch non ci si preoccupa di
instradare A e B verso il sommatore, o verso lo shift, o verso il divisore, ma si inviano A e B in un unico
ingresso. E comodo avere uniti i vari moduli logici che eseguono il calcolo su aritmetica intera perch si
semplifica tutto il progetto dellinterfaccia fra il modulo e gli altri moduli adiacenti. Si ha in questo modo
solo un uscita che viene inviata eventualmente in MEM o in WB, non si hanno uscite. Quando invece si
tengono distinti i moduli in realt si stanno gestendo dei canali di accesso distinti .
E opportuno anche replicare alcuni dei moduli perch quando si sta eseguendo un operazione che la
stessa operazione dellistruzione partita prima, se al precedente non ha terminato, questa operazione verr
stallata. Ad esempio se ci sono due operazioni che effettuano la divisione floating point, la seconda
istruzione dovr comunque attendere che la prima lasci il modulo divisore floating point prima di procedere
la sua fase di EXE, avendo supposto che la divisione floating point duri ad esempio venti colpi di clock.
Allora si pu pensare di replicare alcuni di questi moduli e quali di questi moduli conviene realmente
replicare. E un discorso come al solito di costi e di prestazioni. Quali moduli andrebbero replicati? Quelli
lenti, perch per esempio il sommatore che impiega un numero di colpi di clock abbastanza basso si
potrebbe decidere di non replicarlo perch porterebbe ad affermare che unaltra istruzione che vuole
anchessa usare il sommatore dovr aspettare un numero relativamente basso di colpi di clock, ad esempio
due colpi di clock. Oppure porterebbe a dire che magari il compilatore scheduli le istruzioni in maniera tale
da non mettere subito dopo un operazione di somma floating point un'altra operazione di somma floating
point, inserisce due istruzioni che non utilizzino il sommatore; in maniera tale che quando parte la seconda
somma floating point , il sommatore floating point gi libero.
I moduli che avrebbe senso replicare in realt sono i moltiplicatori e i divisori che sono relativamente lenti
per cui creerebbero delle attese e un progettista tentato a replicare. Per quanto riguarda la divisione per
lhardware del divisore abbastanza complesso, replicando un divisore ci si accorge che nel processore si
va a sacrificare una parte di logica non banale. Il vantaggio si ha qualora ci siano due divisioni floating point,
ad esempio, schedulate prima di 20 colpi di clock tra di loro, lultima invece di aspettare pu subito andare
sullaltro divisore. Ci si accorge per che nei programmi di calcolo che normalmente vengono eseguiti, le
divisioni sono poco frequenti. Tutta una classe di applicazioni scientifiche ha poco a che fare con la
divisione , le operazioni scientifiche che vengono utilizzate il pi delle volte sono i prodotti e le somme. Le
divisioni intervengono per fare una normalizzazione , ma gran parte degli algoritmi scientifici di
elaborazione del segnale, statistiche, non hanno molto a che fare con la divisione. La divisione quindi

105

molto infrequente. Per cui ci si preoccupa, non tanto di migliorare lefficienza della gestione delle divisioni,
soprattutto perch quellhardware del divisore parecchio impegnativo, ma quanto quella dei
moltiplicatori. Quello che in realt avviene che le unit che vengono replicate sono quelle dei
moltiplicatori perch rispetto al sommatore che abbastanza veloce il moltiplicatore non veloce come un
sommatore ma abbastanza frequente. Allora anche se porterebbe maggiore beneficio replicare un
divisore, perch trovare un divisore bloccato comporta un rallentamento non indifferente, si replica il
moltiplicatore perch costa di meno che replicare un divisore. Quindi in generale i processori hanno pi
moltiplicatori e questi saranno anche pipelinizzati.
Unaltro modo di procedere sarebbe quello di pipelinizzare questi moduli: riuscire a disegnare il
moltiplicatore, che necessita di 10 colpi di clock ad esempio, non in maniera rigida come visto prima, ma in
maniera pipeline , dove mentre il prodotto avanza in questo moltiplicatore possibile inserire altri
operandi prima che la moltiplicazione iniziale abbia prodotto il suo risultato. Un po come visto nella
pipeline del processore, come gli operandi hanno liberato una parte del modulo moltiplicatore floating
point, gi altri due operandi possono entrare. Quindi in questo modo si possono disegnare degli oggetti di
in pipeline evitando quindi di bloccare la latenza per tutta la durata delloperazione, facendo in modo che
loperazione successiva pu essere avviata anche prima che loperazione precedente sia terminata.
Laggiunta pi immediata che andava fatta allinizio del discorso quella di un banco di registri
specificatamente dedicato ai numeri floating point, registri F. Dal punto di vista elettronico, i registri
floating point non hanno differenze rispetto a quelli a virgola fissa. Allora sorge la domanda: perch si
usano due banchi di registri e non un unico banco dove inserire registri a virgola fissa e registri a virgola
mobile? Immaginando di avere un processore che abbia trenta registri a virgola fissa e trenta registri a
virgola mobile; questo processore esegue un programma che usa solo dati a virgola mobile, e se si trova
magari a saturare questi trenta registri, costretto a fare operazioni di SWAP, copiare il valore di un
registro in memoria per poi utilizzare quel registro. Con un unico banco utilizzando anche i registri a virgola
fissa si evitava lo swap. Allora perch non c un unico banco con quanti registri si riescono a gestire? Il
problema dovuto al fatto che ci sono dei collegamenti agli elementi in virgola mobile e dei collegamenti
agli elementi in virgola fissa, questi con lALU in sostanza. Quindi con N registri , bisogna collegare questi N
registri a tutti i moduli floating point e a tutti i moduli a virgola intera, si complica la situazione. E quindi
pi comodo avere una parte di registri che collegata a ai moduli floating point e un'altra parte che
collegata allALU. Inoltre i risultati delle operazioni andranno ai rispettivi registri, quindi i moduli sfloating
point saranno collegati ai registri F, perch non con lunit floating point con due operandi sorgenti floating
point il risultato non sar mai un risultato a virgola fissa. Quindi come se esistessero due realt parallele: i
dati a virgola mobile che vivranno nel loro mondo, fatti di registri a virgola mobile e da unit di calcolo a
virgola mobile e i dati a virgola fissa che vivranno nel loro mondo fatti di registro a virgola intera e ALU.
E quindi ci sar un ulteriore unit che sar il banco dei registri per i registri F da distinguere con il banco dei
registri R. I registri che sono collegati in ingresso e in uscita a queste unit, non sono collegati in maniera
banale, perch questi banchi dei registri saranno collegati a queste unit funzionali attraverso stazioni di
prenotazione. Terminata la fase di ID, di un istruzione che esegue un operazione di moltiplicazione floating
point, come gi visto non detto che ci si pu immettere subito nel moltiplicatore, perch potrebbe essere
impegnato. Mentre al termine della fase di ID di un istruzione ad aritmetica intera i latch A e B venivano
subito fatti entrare nellALU direttamente senza alcun problema, visto che lALU era sicuramente libera, per
accedere allunit di calcolo in virgola mobile si necessita di un meccanismo che controller, supervisioner

106

questo accesso. Per cui esiste una logica che far si che gli operandi provenienti dai registri di tipo F
accedano ai moduli floating point e questa logica non banale.

Questo schema esplicita il fatto che la fase somma dura ad esempio 4 colpi di clock, la fase di
moltiplicazione 7 colpi di clock e la fase di divisione 20 colpi di clock, inoltre stato introdotto un
meccanismo pipeline. Per tanto questo meccanismo pipeline agevoler il compito di evitare conflitti
strutturali su queste unit. Con il meccanismo pipeline si pu iniziare una moltiplicazione, e al colpo di clock
successivo iniziarne unaltra, perch questa sar passata in un altro stadio e via dicendo. Non stato ancora
risolto il problema dovuto alla gestione dei conflitti WAW.

107

Il grafico valuta gli stalli prodotti da alcuni benchmarks SPEC89. E un set di benchmark studiati
appositamente per valutare le prestazioni floating point e vedere per un determinato processore quanti
stalli vengono causati a causa delle somme, sottrazioni , conversioni, confronti , prodotti, divisioni. Come
abbastanza lecito aspettarsi , quello che fa scoppiare il panico sono gli stalli dovuti alle divisioni, sia perch
sono le unit pi lente e sia perch sono quelle che mai vengono replicate allinterno del processore.
Il grafico successivo mostra il numero di stalli effettivi per la pipeline in virgola mobile nellesecuzione dei
benchmarks precedenti.

Affinch lesecuzione floating point avvenga correttamente e affinch vengano effettuati una serie di
controlli utili a risolvere i conflitti sia di tipo WAW che le terminazioni fuori ciclo, la pipeline strutturata in
maniera un po diversa. Fondamentalmente il fetch non avviene in un colpo di clock ma viene diviso cos
come anche laccesso alla memoria viene diviso in pi fasi. La fase EXE strutturata con un unico colpo di
clock per c il riciclo nel caso di una fase EXE multi ciclo qualora listruzione floating point.

108

CAPITOLO 12

GERARCHIA DI MEMORIA
1. Panoramica sulle memorie
La memoria viene gestita in realt con una gerarchia di dispositivi di memorizzazione.
Consideriamo il modo di lavorare di un semplice impiegato di ufficio. Limpiegato ha una sua scrivania sulla
quale sono disposti i documenti e gli strumenti che sta utilizzando in un certo momento. Se ha bisogno di
qualcosaltro lo pu prendere dai cassetti della scrivania, piegandosi semplicemente. Potrebbe per aver
bisogno di alcuni libri che sono posti nella libreria, limpiegato deve quindi alzarsi dalla scrivania per andare
a prelevare il libro necessario dalla libreria. E chiaro che, se ha bisogno di utilizzare con una certa frequenza
questo libro appena preso dalla libreria, lo lascia sulla scrivania in modo tale da non alzarsi ogni volta che gli
serve. La libreria, per, potrebbe non contenere tutti i libri che necessitano allimpiegato poich questa ha
anche una sua capienza limitata. Limpiegato quindi ha anche a disposizione una biblioteca posta in una
stanza e, ogni qualvolta si presenta la necessita di utilizzare un libro non presente in libreria, lo preleva dalla
biblioteca che situata un po pi lontana. Quindi limpiegato deve alzarsi e cambiare stanza. Pu capitare
ancora che ci sia la necessit di utilizzare un documento che si trova in archivio, posto addirittura nel
sottoscala.
Il calcolatore lavora allo stesso modo dellimpiegato. I dati che il calcolatore utilizza in maniera immediata
sono caricati nei registri. C una quantit maggiore di dati a cui accede pi velocemente e che sono situati
nella cache. Un'altra quantit ancora pi ampia posta nella memoria centrale e infine ci sono i dischi e le
unit di backup.
Questi livelli di memoria: registri, cache, memoria centrale, dischi e unit di backup sono i livelli della
gerarchia di memoria o piramide di memoria.

La gerarchia di memoria un meccanismo che struttura su dispositivi differenti, con caratteristiche


differenti, la quantit di informazione richiesta dal processore. Questi dispositivi sono:
-

registri posti dentro al processore e sono direttamente accedibili, ma sono in un numero ridotto
109

cache (cache interna e cache esterna, oppure cache di 1 o 2 livello)


memoria centrale, con dimensione pi ampia
dischi
unit di backup

La dimensione dal punto di vista delle informazione da memorizzare in un livello pi ampia della
dimensione di un livello sovrastante. Pi ci si allontana dal processore pi aumenta la capacit, mentre pi
si ci si avvicina al processore pi aumenta il costo riferito alla capacit, costo del byte. Un chip di RAM di
4GB costa 30 ad esempio, mentre un Hard Disk di 1 TB costa 80: il costo del byte minore per lhard disk.
La velocit aumenta pi si vicini al processore.
Questa piramide presenta dei notevoli vantaggi. Il fatto che nei livelli prossimi al processore la capacit
minore comporta una maggiore velocit di accesso a queste aree di memoria. I registri o la cache sono
dentro al processore e si pu dialogare con una certa frequenza. Quando la memoria in un chip distinto
dal processore, esempio la memoria centrale, comporta che i segnali devono viaggiare da un chip allaltro, e
questo presenta problemi fisici, il che comporta a frequenze limite pi basse con il quale posso transitare
questi dati. Ci sono delle capacit parassite dovute a condensatori. Quando si passa da segnale alto a
segnale basso, la commutazione non immediata. Quando si alimenta un segnale a un certo valore si
caricata la capacit parassita e quando viene spento il segnale, questo non va subito a zero, ma bisogna
attendere il tempo di scarica. La frequenza massima utilizzabile data quindi dalla capacit parassita. Letta
una tensione, si associa a un certo livello di tensione alto il bit 1 e a un certo valore di tensione basso il bit
0, considerando sempre una soglia. Il periodo con cui commuta il segnale deve essere ampio rispetto alla
costante di tempo di scarica del circuito, poich pu capitare ad esempio che la scarica di un condensatore
cos lenta che il segnale non giunge al livello zero poich c gi il picco dellaltro livello logico, e in
conclusione si legge sempre il bit di valore alto. Quando si passa da un chip a un altro si aggiungono altre
capacit parassite che diminuiscono le frequenze di comunicazioni. Dentro lo stesso chip, invece, si viaggia
a velocit pi sostenute. Quindi importante notare quando si dice che un certo processore ha un
frequenza di un certo numero di clock, e se si vuole aumentare la frequenza del clock, bisogna stare attenti
che con la nuova frequenza data allinterno non accadano problemi, poich come detto prima vengono letti
erroneamente i livelli logici dei bit trasmessi.
Per velocizzare il trasferimento si pu pensare a parallelizzare i bit, attraverso piste parallele si inviano bit in
parallelo. Questo funziona bene quando si allinterno dello stesso chip. Quando bisogna invece dialogare
con chip diversi bisogna inserire un certo numero di piedini dovute alle piste. Aumentare la piedinatura di
un circuito elettronico crea un problema. Per un doppia word sarebbero necessari ad esempio 64 piedini
per i 64 bit. Allora conviene inviare i bit serialmente inserendo quindi un ritardo.
Non sempre opportuna quindi allargare la punta della piramide. Inoltre con registri con dimensioni
elevate comporterebbe maggiore informazione e quindi il tempo per il processore per cercare un dato che
interessa aumenta.

110

2. Principi generali
Affinch il livello funzioni, bisogna garantire dei principi generali che garantiscono il regolare
funzionamento:
-

Ogni livello trasparente al processore. Il processore non sa che sotto di lui c una cache di primo
livello o secondo e che poi c la memoria centrale e cos via. Il processore sa che sotto di lui c una
sola memoria. Effettivamente il processore da un solo indirizzo di memoria, non chiede il dato
presente in memoria centrale o in cache, chiede un dato dalla memoria con un indirizzo. Per il
processore, quindi, esistono i registri e la memoria. Per cui i livelli di memoria simulano per i livelli
sottostanti il processore e per i livelli sovrastanti invece simulano la memoria. Il meccanismo che
quando il processore necessita di un dato questo lo chiede alla memoria. Questa richiesta
soddisfatta dallunit di memoria pi vicina, la cache. Il dato non transiter mai dal disco
direttamente al processore. Quindi il processore necessita di un dato che lo preleva dalla cache. Se
la cache contiene il dato lo manda al processore, ma se nella cache non presente il dato richiesto,
visto che la cache ha una dimensione ridotta, verr avviato un meccanismo di gestione della
memoria, tipico del sistema operativo, che staller il processore, si tratta di stallo per mancanza di
pagina, e nel frattempo trova quel dato dalle memorie sottostanti e lo restituisce al processore. Se
il processore multi task o multiutente invece di stallare esegue altri task. Un processore multi task
esegue pi processi in parallelo, cio per alcuni colpi di clock esegue un certo processo, per altri
colpi di clock esegue un altro processo. Quando un certo processo deve stopparsi, per input o
output ad esempio, il processore nel frattempo elabora un altro processo. Si tratta di cambio di
contesto, i registri utilizzati per il vecchio processo vengono memorizzati in modo tale che questi
registri poi vengano utilizzati per il nuovo processo. Il sistema pu anche essere multiutente, il
processore pu lavorare per una fetta di tempo (slice) per un certo utente, e per un'altra slices per
un altro utente. I supercomputer hanno processori cos potenti che sono usati per accontentare in
contemporanea pi utenti. Il meccanismo di cambio di contesto pi delicato in questi casi.
Quando questi processi in gioco devono fare operazioni di input output o anche gestione di stalli
per mancanza di pagina, invece di far stare il processore fermo per alcune frazioni di tempo, il
processore lavora su un altro processo. Quando si preleva un istruzione, il processore da il PC alla
memoria per ottenere listruzione da eseguire. Se la cache non ha listruzione di quellindirizzo in
memoria, il processore si staller per un certo tempo (ere geologiche per il processore) o eseguir
altri task, e la cache prelever il dato, listruzione in questo caso, dalla memoria centrale, e se
questa non ce lha dovr andarlo a prendere dal disco rigido. Ai livelli intermedi ogni livello simuler
quindi per i livelli sottostanti il processore. La cache che chiede il dato alla memoria centrale, si
comporta come il processore che chiede un dato e via dicendo finch non stato trovato il dato.
Una volta trovato il dato, per esempio nella memoria centrale, questa lo da alla cache, la quale
sveglia il processore dallo stallo e consegna il dato.
Tutto il meccanismo cerca di realizzare lhelldorado: memoria di ampia capacit e alta velocit.
Questi due compromessi non possono coesistere ma si cerca una situazione intermedia. Si cerca di
creare un meccanismo che si avvicini il pi possibile a questa situazione. Perch altrimenti bastava
avere un processore con dei registri e un disco rigido contente tutti i dati. Avere una sola memoria,
il disco rigido per esempio, comporta che la lettura avvenga con il tempo necessario del disco
rigido. Con la gerarchia di memoria, invece, il tempo quello della cache. Nel punto precedente
per si detto che se la cache non ha il dato questo deve essere prelevato dalle memorie

111

sottostanti e quindi una perdita di tempo. Per questa perdita di tempo avverr solo quando il dato
verr chiesto per la prima volta, perch una volta richiesto dalle memorie sottostanti, il dato verr
copiato in cache, e quindi la prossima volta in cui servir verr preso direttamente dalla cache
velocemente. Per realizzare questa condizione ottimale si sfruttano due fenomeni che non sono
dimostrabili matematicamente, ma sono comunque veri dal punto di vista logico:
o Principio di localit temporale: quando una cella di memoria viene utilizzata, probabile
che presto venga utilizzata di nuovo. Quando un processore chiede un dato allindirizzo x,
probabile che il dato allindirizzo x sar richiesto di nuovo. Basti pensare che se lindirizzo x,
lindirizzo di una istruzione allinterno di una iterazione, fin quando non si esce dal loop,
quellistruzione servir sempre. Oppure se c un programma che elabora dei dati,
difficilmente il programma elaborer un dato una sola volta.
o Principio di localit spaziale: quando una cella di memoria viene utilizzata, le celle adiacenti
hanno un alta probabilit di essere utilizzate di li a proco. Basti pensare alle istruzioni di un
programma che sono poste in memoria una dopo laltra proprio per il meccanismo del PC
che si auto incrementa; almeno che listruzione non sia un salto, sicuramente dopo aver
prelevato un istruzione a un certo indirizzo, dopo verr prelevata quella allindirizzo
successivo. Quindi prelevata listruzione a un certo indirizzo, la prossima a essere prelevata
sar sicuramente quella allindirizzo successivo. Oppure si pensi a una matrice, o un
vettore. Nellambito di una iterazione verr processato lelemento i-esimo, alla prossima
iterazione verr processato lelemento i+1-esimo. Se in memoria gli elementi dei vettori
sono caricati in maniera contigua, elaborato lelemento allindirizzo x, lelemento
successivo che verr elaborato sar allindirizzo x+8, se le celle sono di 8 byte per esempio.
Queste considerazioni suggeriscono lorganizzazione delle memoria in blocchi (o pagine). Un blocco una
porzione della memoria comprendente un certo numero di celle di memoria contigue. Quando al
processore serve il dato allindirizzo 1000, lo chiede alla memoria cache, e se questa non ha il dato
allindirizzo 1000, lo chiede alla memoria centrale, la quale a sua volta se non ha il dato lo chiede al disco e
cos via. Questa operazione di carica di un dato nella memoria, ha una certa latenza. Chiesto il dato a un
indirizzo alla cache, questa se non ha il dato mette in stallo il processore, reperito il dato dopo un certo
tempo lo si fornisce al processore. Questo tempo trascorso dovuto al tempo in cui il livello n chiede al
livello n+1 il dato, invia una richiesta, il livello al suo interno legge il dato e trasferisce in uscita il dato al
livello che glielo ha chiesto, nellipotesi che questo ha il dato. Questo tempo non proporzionale alla
quantit di dati richiesti. Se invece di chiedere un dato se ne richiedono 50, il tempo necessario non 50
volte maggiore. In realt il tempo non proporzionale alla quantit di dati, solo un certo tempo lo .
Quindi, chiesto il dato x al livello sottostante, il livello sottostante invece di dare solo il dato x, da anche
tutto il blocco contente il dato x, perch per il principio di localit spaziale, servir di li a poco il dato x+1 e
quindi dandolo adesso successivamente non dovr chiederlo. Riprendiamo il discorso sul tempo
considerando cosa succede in un hard disk. Il tempo di lettura di 1 byte dal disco rigido identico al tempo
di lettura di 100 byte. Il disco rigido composto da un disco diviso in piste, e il disco ancora diviso in
settori. La parte del sistema operativo gestore dei dischi, associa a un indirizzo il settore e la pista dove il
dato situato. Per prendere il dato quindi bisogna leggere dalla pista n del settore m. La lettura avviene
con una testina, costituita da una coppia di magneti, che rileva un campo magnetico che passa sotto di lei
(bit 0 spin in un senso, bit 1 spin nellaltro senso). La testina si muove lungo un braccio, che si posiziona
sulla pista che contiene il dato. Il disco girando sposta i settori sotto la testina. Il settore cercato, quindi,
passa sotto la testina e il dato viene letto. Il tempo di lettura del settore dato dalla velocit di rotazione
112

del disco e dalla dimensione angolare del settore. Ad esempio, un disco diviso in 10 settori, e con una
velocit di 7200 rpm, cio a significare che in un minuto si fanno 7200 giri, in un secondo si fanno 120 giri, e
siccome un settore 1/10 di giro, in un secondo passano 1200 settori sotto la testina, oppure lo stesso
settore passa 120 volte sotto la testina. Un settore viene letto quindi in circa 1/1000 di secondo. Il
problema solo arrivare con la testina sulla pista, tempo di trascinamento, in pi bisogna aggiungere il
tempo necessaria a far arrivare il settore. Da quel momento in poi passa 1/1000 di secondo per leggere
tutto il settore. E chiaro che conviene leggere un grosso numero di dati piuttosto che un solo dato, perch
qualora servir il secondo byte di quel settore, nel frattempo la testina si sar mossa e bisogner riportare
la testina sulla pista e sul settore giusto e quindi perdere nuovamente tempo. Come si vede il tempo non
dipende tutto dalla dimensione dei dati, solo una certa parte del tempo. Conviene quindi pagare poco pi
tempo e inviare pi byte. Nella deframmentazione del disco si scrivono i frammenti di file tutti nello stesso
settore, e magari nella stessa pista in maniera contigua, in modo che nella lettura di un file la testina non
deve spostarsi pi di tanto per leggere un file. Perch i file vengono inserirti in maniera disordinata? Perch
durante la scrittura magari non si trovato uno spazio contiguo a contenere quei dati, poich prima cera
qualcosa che poi stato cancellato, e allora i dati sono stati frammentati e scritti in settori e piste diverse.

Bisogna ora gestire quattro problemi:


1. Allocazione del blocco: dove possibile mettere un blocco in un livello superiore quando questi lo
carica dal livello inferiore? Quando un livello di memoria chiede un blocco che contiene il dato
necessario dal livello inferiore, dove lo posizioner?
2. Identificazione del blocco: come possibile trovare un blocco nel livello di memoria? Posizionato il
blocco, quando si chieder un dato di quel blocco, il blocco dove si trova?
3. Sostituzione del blocco: quale blocco deve essere sostituito in caso di fallimento di accesso per fare
posto a un blocco referenziato? Supponiamo che un certo livello di memoria prenda un blocco dal
livello sottostante. Se questa memoria, quella che chiede il blocco, piena, dove va messo questo
nuovo blocco? Bisogna sostituire un blocco per fare spazio e inserire questo, ma quale blocco va
sostituito?
4. Scrittura del blocco: cosa succede nel caso di modifica del contenuto di un blocco? Quando viene
aggiornata una variabile, e quindi un blocco viene modificato, cio una locazione allinterno del
blocco viene modificato, cosa succede? Se questa modifica avviene in un blocco della cache, alla
copia di quel blocco in memoria centrale cosa succede? Il dato in memoria centrale viene
aggiornato o no, cio resta scaduto?

113

114

CAPITOLO 13
MEMORIA CACHE

I vari livelli della piramide si scambiano dei blocchi. Quando un livello non ha un dato, preleva dal livello
sottostante un blocco che lo contiene, con la filosofia per cui dato che bisogna prendere quello che serve,
opportuna anche prendere qualcosa che forse servir.
Ora vediamo le 4 domande. La risposta di queste 4 domande viene data a seguito di una data strategia nel
momento in cui io copio un dato nel livello superiore. Queste strategie sono diverse per ogni livello di cui
parleremo. Tipicamente quello che faremo nei livelli pi alti diverso da quello che faccio nei livelli pi
bassi. Avendo parlato del processore, ci interessiamo per quello che riguarda il livello delle MEMORIE
CACHE.
1. Cenni storici sulle memorie cache
Il termine cache indica qualcosa come se fosse un cassetto. Nel 1965 si comincia a parlare di memorie
cache in un articolo di M. Wilkes, in cui si ipotizza questo modello di memoria e si gettano le basi su tutto il
sistema piramidale. Wilkes tra laltro anche lo scienziato che ha inventato il controllo del processore
microprogrammato. Lunit di controllo pu essere fatta con due approcci:
Unita di controllo cablata e micro programmata. Lultima parte dallidea di descrivere quello che avviene
allinterno del processore in ogni fase come se questo macchinario a sua volta un processore. Quindi tutto
quello che deve avvenire passo per passo avviene in un programma che prende il nome di
microprogramma.
Nellabstract di questo articolo si legge:
Si discute sullutilizzo di una memoria veloce di 32 K parole, supporto di una memoria centrale pi lenta,
diciamo di 1M parole, in modo che in pratica il tempo di accesso effettivo sia pi vicino a quello della
memoria veloce che a quello della memoria lenta
La memoria cache stata realizzata 3 anni dopo. Grazie a una
tecnologia dei diodi tunnel. Le memorie che prima venivano
realizzate con nuclei di ferrite possono avere un livello fatto con
tecnologia pi veloce. Alluniversit di Cambridge realizzarono lIBM
360.

Il termine cache spesso abbreviato con il simbolo $.


2. Strategie di allocazione dei blocchi
Quando si fa riferimento a una istruzione o ad un dato x non presente nella cache necessario caricarlo
dai livelli sottostanti . Questo un discorso valido per ogni livello. Il problema dove allocare il blocco
contenente x? Ci sono tre strategie diverse per risolvere questo problema. Supponiamo di avere una

115

memoria sottostante alla cache di 32 blocchi, e che ogni blocco sia costituito da 64 byte, e di avere una
cache con soli 8 blocchi. Parleremo di linee, cio le aree della cache dove caricare i blocchi.
2.1. Memoria Completamente Associativa
Il blocco pu essere caricato in qualsiasi linea della cache . Si chiama associativa perch un blocco
associato in maniera completa a tutta la memoria. Non c un problema di scelta. Con una cache di questo
genere nasce il problema durante la lettura di un dato, in cui non sapendo su quale linea stato collocato
un blocco, si perder tempo nel cercarlo.
2.2. Indirizzamento Diretto
Il blocco pu essere associato ad un'unica linea della cache. Questo implica che se la linea a cui associato
un blocco occupata, il nuovo blocco sovrascrive quello che precedentemente vi risiedeva. E se
successivamente serve il dato nel blocco appena sovrascritto bisogna prelevarlo scriverlo al posto di quello
caricato ultimamente, i due blocchi si sovrascriveranno a vicenda. Con una gestione di questo tipo la lettura
di un blocco semplice. Il blocco quando stato caricato potrebbe essere stato copiato solo in una
determinata linea. La ricerca sar molto pi veloce. Il problema quindi dovuto solo alla scrittura e quindi
alla conseguente sovrascrittura dei blocchi. La corrispondenza che c tra un blocco e la linea nella cache
data dalla relazione:

Quindi i blocchi 16 e 24 andrebbero a stare sulla stessa linea e quindi a sovrascriversi.


2.3. Set Associativa
Rappresenta una via di mezzo tra le due strategie appena analizzate. Lidea questa: poich lidea di
mettere un blocco su una linea a caso conveniente a discapito dellassociazione diretta tra un blocco e
una linea, per nel cercare un blocco non bisogna guardare tutta la memoria cache, allora si divide la
memoria in dei set, ogni set contiene qualche linea, ad esempio 4 set da due linee, e si associa al blocco un
set. Quando bisogna caricare un dato della memoria cache lo si va a cercare nelle linee di un determinato
set, quello associato allindirizzo di quel blocco. Il blocco e il set sono associati tramite la relazione:

Quando si carica un blocco nella cache, lo si carica in una linea a caso del determinato set associato a quel
blocco. Si vede se presente una linea libera fra quelle di questo set e lo inserisce li. Se non ci sono linee
libere, allora si dovr sovrascrivere un blocco. Quindi nella ricerca di un dato blocco, non si dovr cercare in
tutte le linee il blocco che contiene quel dato, ma si dovranno guardare i blocchi nelle linee di un set
preciso.
Una cache set associativa che ha tanti set quante sono le linee, una memoria cache a indirizzamento
diretto; una cache set associativa che ha un solo set una memoria cache completamente associativa.
3. Strategia di ricerca e identificazione del blocco
Come ricercare e identificare la linea della cache che contiene unistruzione o un dato x? Quando il
processore chiede un dato alla cache, questa deve capire se questo dato ce lha o meno. E chiaro che per

116

vedere se questo dato ce lha deve ricondursi a quello che ho fatto quando ha preso il dato dalla memoria
centrale e lha copiato in cache, per cui dovr sapere se si tratta di una memoria cache set associativa,
associativa diretta o completamente associativa, per sapere appunto su quale linea cercare il blocco con il
dato.
Bisogna trovare un sistema per capire come si fa a determinare quale blocco c in quella linea. Questa
domanda implica anche: come si fa a sapere se il blocco contenente x presente in cache? Si utilizzano le
etichette che contengono alcune informazioni. Ad ogni linea della cache associata unetichetta che
permette di sapere fra laltro quale blocco contenuto in esso. Come fata letichetta? Per ora ci che
occorre sapere che una delle informazioni presenti nelletichetta il numero del blocco contenuto in
quella linea stessa. Quindi dato un indirizzo, togliendo i bit meno significativi che sono quelli relativi
allindirizzo interno al blocco, se la cache completamente associativi tutti questi bit identificano letichetta
e quindi la linea; se la cache a indirizzamento diretto un certo numero di bit meno significativi individuano
la linea e i restanti pi significativi letichetta; se la cache set associativa alcuni bit meno significativi
individuano il set e i restanti la linea in quel set.
Consideriamo lindirizzo x = (001110)2
I puntini indicano i bit che identificano il dato nel blocco. Se il blocco contiene solo 4 byte , al posto dei
puntini ci sono 2 bit. Questi servono per la ricerca del dato nel blocco
Per una memoria completamente associativa, letichetta tutto 001110. Questi bit individuano il blocco
allinterno della cache.
Per una memoria a indirizzamento diretto a 8 linee, i 3 bit meno significativi individuano la linea, quindi 110
identificano la linea in cui contenuto il blocco. Letichetta, quindi il numero del blocco dato dai restanti
bit 001.
Per una memoria set associativa, con 2 set e 4 linee, i due bit meno significativi 10 individuano il set,
mentre i restanti bit individuano letichetta, quindi la linea in sostanza il blocco allinterno del set.
Questo significa che dovr cercare nelle etichette. Mentre prima risultava attraente questa soluzione ,
perch ti dava la possibilit di avere 8 chance, quando mi preoccupo di cercare un dato la completamente
associativa mi implica la necessit di cercare in tutte queste etichette se per caso io ho a che fare con un
etichetta uguale a questa.
4. Sostituzione di un blocco
Consideriamo il caso in cui la cache carica dalla memoria sottostante un blocco, per questa satura se
stiamo parlando di una memoria completamente associativa, oppure lunica linea disponibile per quel
blocco occupata se ci stiamo riferendo a una cache a indirizzamento diretto, o tutte le linee del set
associato a quel blocco sono piene nel caso di una cache set associativa, quale blocco deve essere sostituito
dal nuovo? Questa domanda ne richiede anche unaltra prima: come si fa a sapere se una linea libera o
meno?
Per sapere se un certo blocco di cache libero o meno si usa un bit aggiuntivo nelletichetta: VALIDITY BIT.
Il validity bit vale 1 se il blocco contenuto nella linea valido, se invece la linea vuota vale 0, intendendo

117

che questa linea non valida e quindi vuota. Quindi letichetta oltre a contenere il numero del blocco ha
anche un bit per indicare se quello che c scritto sulla linea valido o meno.
Ritorniamo alla sostituzione di un blocco. Se la cache a indirizzamento diretto, il blocco da sostituire
solo uno, non bisogna scegliere tra diversi blocchi. Un blocco deve andare in quella unica linea che nel caso
contenga un altro blocco, questo dovr essere sostituito.
Per una cache completamente associativa o set associativa invece si procede attraverso delle strategie di
sostituzione:
-

RANDOM: viene scelto il blocco a caso


LRU (Least Recently Used): viene sovrascritto il blocco meno recentemente usato. Se vero che
vale il principio di localit spaziale e temporale preferibile sostituire un blocco che non viene
utilizzato da diverso tempo piuttosto che sostituire un blocco che probabilmente dovr essere
riutilizzato di li a poco. Ci vogliono delle risorse hardware per realizzare questo. Una possibile
realizzazione potrebbe essere quella di inserire un ulteriore campo nelletichetta, un campo
contatore che ad ogni accesso alla cache che non vede protagonista quel blocco incrementi il
valore del suo campo contatore, qualora venisse utilizzato invece si resetti il contatore.
FIFO: una semplificazione dellLRU. Tiene traccia dei blocchi vengono utilizzati di volta in
volta. Questo meccanismo fatto in maniera molto semplice: nella memoria cache c uno shift
register, una cella collegata ad un'altra e via dicendo, in cui un flusso avviene come in una
pipeline, questo shift register ha una sua lunghezza, quando si accede a un blocco, faccio
entrare a uno shift register, lidentificativo di quella linea. Il primo ad essere entrato il primo a
uscire.

5. Scrittura e lettura di un blocco


Le scritture sono meno frequenti delle letture per cui prima di occuparci delle scritture dobbiamo
soffermarci sulle letture che sono il tipo di accesso pi frequente. Il fatto stesso che letture sono
loperazione pi frequente comporta una gestione del meccanismo di lettura il pi veloce possibile:
vengono letti in parallelo tutte le parole di ogni blocco, e contemporaneamente avviene la lettura
delletichetta, si verifica se il blocco nella linea coincide con il blocco cercato. In questo modo si risparmia
tempo, perch se l'etichetta coincide, il blocco stato gi letto, altrimenti se non coincide non si fatto
nulla di dannoso. In questo modo si ottimizza il processo perch contemporaneamente si identifica la linea,
si legge la parola che si trova in quel blocco a quell'indirizzo e durante la lettura della parola avviene il
controllo dell'etichetta per verificare che quello che si sta leggendo coincide con quello che si sta
effettivamente cercando. Quindi la lettura di un blocco dalla cache avviene in contemporanea alla lettura e
al confronto della sua etichetta, in questo modo la lettura inizia non appena disponibile lindirizzo del
blocco.
Questo meccanismo non attuabile nel caso di una scrittura. La scrittura di un blocco non pu cominciare
prima che sia terminata la verifica della sua etichetta. Considerando il procedimento prima descritto,
avviene la scrittura del dato e la verifica delletichetta in contemporanea, e se l'etichetta diversa da quella
desiderata, stato scritto un dato all'indirizzo sbagliato, facendo in questo caso qualcosa di dannoso. Quindi
necessario prima identificare letichetta corretta e poi procedere con la scrittura. Per fortuna questo
ritardo dovuto alle scritture non molto frequente visto che le scritture sono pi rare delle letture.

118

Quindi bisogna valutare l'etichetta prima di scrivere. Se l'etichetta non coincide bisogna caricare il blocco
dalla memoria sottostante.
A prescindere dal fatto di vedere se il blocco presente o meno, bisogna considerare anche un altro
aspetto: quando si aggiorna un dato nella memoria cache, considerando che quello che sta nella cache
proviene dai livelli di memoria sottostanti, ci si imbatte in una situazione anomala perch il dato avr un
valore nella cache e un altro valore nei livelli sottostanti, poich ad esempio in memoria centrale non
stato aggiornato il dato. In alcune circostanze questo potrebbe non essere un problema, perch quando il
processore avr bisogno del dato comunque lo andr a prendere dalla cache, dove il dato effettivamente
aggiornato. I problemi sorgono quando il processore ha aggiornato il dato nella cache ma ad un certo punto,
richiede un nuovo blocco e questo blocco va a scriversi nella memoria cache sovrascrivendo il dato
precedente. A questo punto l'aggiornamento che il processore aveva fatto nella cache stato
completamente perso, e la prossima volta che il processore richieder quel dato dalla cache otterr un dato
scaduto e non pi quello aggiornato.
A questo punto bisogna gestire questo problema. Quindi prima di sovrascrivere un dato bisogna verificare
se questo stato modificato allinterno della cache, e nel caso sia stato modificato non bisogna scriverci
subito sopra, ma va scritto prima sulla memoria centrale, sovrascrivendo la sua vecchia versione in memoria
centrale, e poi sovrascritto in cache. Bisogna innanzitutto capire se un blocco in cache stato scritto o meno
dal processore. Ci aiuta in questo l'etichetta che contiene il bit di modifica che a seconda dellarchitettura
del processore prende il nome di change bit, clean bit o dirty bit. I bit di modifica change bit e dirty bit con il
valore 1 indicano che quel blocco stato sporcato e quindi modificato, viceversa con 0 indicano che quel
blocco non stato toccato; per il clean bit, invece, con il bit 0 si indica che il blocco stato modificato.
Ci sono per dei sistemi che lavorano con pi processori in parallelo. Consideriamo il caso in cui un
processore copia un blocco nella sua memoria cache da una memoria centrale comune a tutti i processori, e
modifica questo dato nella sua cache. Se un secondo processore preleva successivamente lo stesso dato
dalla memoria centrale dopo che il primo ha modificato quel dato nella sua cache, il secondo prender un
dato scaduto. Quindi se fino ad ora ci siamo preoccupati che un processore abbia nella sua memoria
cache dei dati coerenti attraverso il clean bit, ora bisogna gestire un sistema che oltre a scrivere in cache
scrive anche in memoria centrale ad ogni aggiornamento, in modo da avere una coerenza di dati nella
memoria centrale.
Per risolvere questi problemi ci sono due politiche si scrittura: Write back e Write through.
La modalit write back scrive in memoria centrale il dato aggiornato dalla cache solo quando si sta per
sovrascrivere quel dato con un altro blocco. Nel momento della sostituzione di un blocco nella cache, se
questo ha il dirty bit settato al valore 1, ad indicare che un dato in quel blocco stato modificato, allora
questo viene copiato in memoria centrale e poi sovrascritto.
Write through invece attraverso la memoria cache scrive anche in memoria centrale. Quando si aggiorna un
dato nella cache, questo viene contemporaneamente sia aggiornato in cache e sia nella memoria centrale, a
differenza della write back che necessita, prima di scrivere in memoria centrale, che il blocco venga
effettivamente sovrascritto in cache.
La seconda alternativa serve in sistemi multiprocessore o in sistemi che gestiscono l'input/output sulla
memoria e devono prevedere la possibilit di accedere ai dati della memoria sempre aggiornati. Ci sono
119

per altre complicazione ad esempio se la CPU1 prende il dato x e la CPU2 prende anchessa il dato x; se
CPU1 varia x, nella cache di CPU2 c' il vecchio x e non quello aggiornato.[cit. Fatti mandare dalla
mamaaaaaaaa a prendere il latteeeeeeeee(latte scaduto)].
La CPU, anche se la logica di write through, non scrive in memoria centrale, sar sempre la cache a
scrivere nella memoria centrale mantenendola sempre aggiornata.
Una write back dal punto di vista del traffico in memoria centrale molto pi efficiente, perch il dato viene
aggiornato solo prima di venire sovrascritto, facendo 50 modifiche nella cache, solo una arriver alla
memoria centrale a differenza della write trough.
Altro aspetto che la write back implica la scrittura in memoria di un blocco, mentre nella write through
viene scritto solo il dato e non tutto il blocco, senza l'etichetta praticamente. Quest'ultimo procedimento
ovviamente potrebbe non essere conveniente, per si possono strutturare le memorie non con un solo
change bit per blocco, ma con tanti change bit quante sono le parole del blocco, cos che quando bisogna
sovrascrivere un blocco in memoria centrale si effettua la copia solo delle parole del blocco il cui change bit
uguale a 1. Con questo approccio abbatto il traffico in memoria per pago la gestione di questa logica,
adesso infatti i bit da controllare sono molti di pi.
Nella write back la velocit di scrittura quella della cache e le scritture multiple richiedono comunque una
sola scrittura nella memoria sottostante. Questo significa che a parit di banda di memoria centrale, con
una write back si possono avere pi processori collegati alla memoria centrale, per come detto prima la
write back trova problemi in logiche multiprocessore, per si pu ovviare il problema strutturando un
algoritmo parallelo in modo tale che gli stessi processori operano sullo stesso processo ma dividendosi
rigorosamente i dati sui quali lavorano. In questo modo se un processore modifica un determinato dato, gli
altri processori non hanno il problema di acquisire dati scaduti, poich non operano su quel dato, ma su
altri dati, un esempio il filtraggio di un immagine durante il quale l'immagine viene divisa in tante parti
ognuna di queste viene affidata ad un processore.
Write through invece ha come vantaggio che avendo fatto in linea l'aggiornamento della memoria centrale,
non bisogna preoccuparsi di aggiornare la memoria centrale quando si prende un blocco che si deve
sovrascrivere a quello in cache,tanto sia che sia stato modificato sia che non sia stato modificato, comunque
stato riscritto nella memoria centrale, questo ovviamente un vantaggio per i sistemi multiprocessore o
per i sistemi I/O sui dati mentre questi sono in elaborazione.
Ora vediamo per come migliorare la scrittura per quanto riguarda il tempo che ci vuole per eseguirla, ad
esempio per una write through importantissimo ridurre il tempo di scrittura. Quindi non sar il processore
a scrivere in memoria centrale, il processore scrive in cache e una circuiteria della cache si preoccupa di
scrivere in memoria centrale, cos la scrittura arriva alla velocit della cache e una parte della cache gestir i
rapporti con la memoria centrale. Si utilizza un meccanismo che prevede lutilizzo di un buffer. Il buffer di
scrittura una memoria di tipo FIFO, in cui quando il processore scrive sulla cache scrive anche in questo
buffer, perch non detto che durante la scrittura sulla cache, questa pu trasferire il dato
immediatamente sulla memoria centrale. Ad esempio se nella cache scriviamo tanti dati consecutivi, la
cache non pu scrivere i dati nella memoria centrale alla stessa velocit, con la stessa frequenza, anche
perch il processore scrive un dato a colpo di clock che troppo veloce per la memoria cache. Quindi si
utilizza il buffer per immagazzinare temporaneamente le modifiche e poi quando la cache ha tempo svuota
il buffer in memoria centrale. La memoria centrale per dar priorit alla cache che vuole leggere dei dati,
120

non che per svuotare il buffer si fermano le letture, tranne in casi di emergenza, ad esempio se il buffer
pieno e si rischia di perdere i dati successivi che dovrebbero essere scritti nel buffer.
Il buffer semplicemente una coda che ha oltre allo spazio necessario a contenere i dati, ha dello spazio
dedicato alle etichette, perch bisogna sapere dove deve andare il dato che sta nel Buffer, e quindi servono
gli indirizzi.
Nel buffer succede che quando dalla cache arriva un nuovo dato da accodare, e questo una copia pi
aggiornata dello stesso dato che ancora nel buffer, e in questo ci aiuta l'etichetta, stesso dato stessa
etichetta, viene rimosso dal buffer quello meno aggiornato, perch non sarebbe opportuno scrivere in
memoria una versione di x che verr cambiata poco dopo.

121

122

CAPITOLO 14

PRESTAZIONI DELLA CACHE


In questo capitolo studieremo i fallimenti che si hanno per vari tipi di cache sia come dimensione, sia come
grado di associativit, 1 via, 2 vie e cos via, considereremo cache set associative a 2 vie etc.
Data una dimensione ad esempio di 1kB, il miss rate totale, cio numero di fallimenti totale, diminuisce
aumentando il numero di vie, cio il grado di associativit che dato dal numero di blocchi all'interno di un
set. Fissata la dimensione e raggruppati i blocchi in un certo numero di set, ad esempio raggruppandoli ad
indirizzamento diretto, il che vuol dire 1 via, c 19% di miss rate, aumentando i blocchi la percentuale
scende. Quindi aumentando le vie, diminuisce il miss rate.
Il miss rate diminuisce anche aumentando la dimensione della cache a parit di vie.
E' interessante capire da cosa sono costituiti questi miss rate. Si classificano le cause che portano ai
fallimenti in tre categorie: classificazione delle tre C. Ci sono miss rate obbligatori (compulsory), di
capacit(capacity) e di conflitto(conflict).
I fallimenti obbligatori si riferiscono alla richiesta di un dato non ancora presente in memoria, e sono
indipendenti dalla dimensione della cache e dal numero di vie. La dimensione della cache, come anche le
vie, non influiscono su questo tipo di fallimento.
I fallimenti di capacit, si ottengono quando la cache non pu contenere tutti i blocchi necessari durante
lesecuzione di un programma, si parla di mancanza di capacit. Il numero di fallimenti diminuisce con
laumentare della dimensione della cache e aumenta con laumentare dellassociativit.
Gli errori di conflitto si hanno quando bisogna necessariamente sostituire dei blocchi nella cache perch
l'indirizzamento punta a blocchi gi occupati, nonostante nella cache ci sia spazio libero.
Questi fallimenti diminuiscono all'aumentare delle vie.
Ricapitolando: i fallimenti obbligatori dipendono solo dal programma; quelli di capacit dipendono dal
programma e dalla dimensione della cache; quelli su cui c pi libert sono quelli per conflitto poich
all'aumentare dell'associativit si riduce il numero dei fallimenti, non necessario arrivare all'associativit
assoluta, in genere basta arrivare a 8 vie.
1. Analisi delle prestazioni della cache
Le cache di sole istruzioni e soli dati sono praticamente necessari perch su due circuiti diversi si sistemano
dati e istruzioni, che sono di tipo diverso, permettendo di accedervi contemporaneamente. Dividendo
anche le cache in questo modo, si permette di progettare queste memorie in modo ottimizzato per
l'accesso alle istruzioni e l'accesso ai dati. Perch l'accesso alle istruzioni leggermente diverso dall'accesso
ai dati, dal punto di vista statistico. Posso per esempio pensare di dare un grado di associativit maggiore ad
una cache di dati, dove un po' meno valido il principio di localit spaziale e temporale, a differenza della
memoria delle istruzioni dove questo principio pi affidabile. Siccome una cache con grado di associativit
maggiore pi costosa di una con associativit minore, posso pensare di progettarle in modo diverso.
Anche perch queste cache sono integrate nel processore. Attualmente i processori hanno 2 livelli di cache
123

interni, 2 punte della piramide.


Strutturando le 2 memorie separatamente, a seconda della dimensione, si ha un miss rate pi basso!
Ovviamente il miss rate sulla cache delle istruzioni pi basso di quello dei dati.
Adesso facciamo alcune considerazioni sulle prestazioni. Il modello di valutazione di una prestazione di
cache legato a tutto quello che avviene nel sistema, non interessa avere un sistema in cui la cache fallisce
allo 0%, quello che pi interessa il tempo in cui viene eseguito un processo.
A parit di condizioni, il tempo CPU fortemente legato, non solo al tempo in cui la CPU esegue un
istruzione ma anche al tempo di accesso/scrittura/lettura ecc.

I cicli di CPU sono quei colpi di clock che contiamo quando abbiamo le istruzioni, contando gli stalli legati
alla gestione dei dati(load/store ecc.) Nel diagramma non abbiamo mai messo i fallimenti legati alla
memoria, perch se il dato non in memoria il colpo di clock slitta, perch alcuni colpi di clock andranno a
vuoto per permettere il caricamento dalla memoria centrale alla cache. Questi cicli si possono quantificare
con la percentuale di miss rate, che va moltiplicata per le letture del programma, di solito si ha il miss rate
per le scritture e il miss rate per le letture. Il miss rate delle scritture solo il miss rate scrittura dati, perch
di solito pi alto di quello delle letture.
Considera che il missrate di lettura si divide in miss rate lettura dati e miss rate lettura istruzioni (N.B. Per il
missrate scrittura, manca ovviamente il missrate scrittura istruzioni), il miss rate sui dati pi importante
del miss rate sulle istruzioni.
Supponiamo ora di avere un miss rate di 3/100, ad indicare che se il programma accede in memoria 100
volte, si ottengono 3 fallimenti! Quanti cicli durano quei 3 fallimenti? Vanno moltiplicati per le
penalizzazioni di fallimento di lettura/scrittura.

La penalizzazione il tempo sprecato ogni volta che si ottiene un fallimento di memoria.


Quanto dura questa penalit di fallimento? Dipende, perch bisogna capire che se la cache non trova il dato,
lo va a prendere dalla memoria centrale, il tempo perso a fare questa operazione dipende dalla memoria
centrale a questo punto. Il problema che questa penalit non facile da calcolare, perch il dato potrebbe
anche non essere nella memoria centrale, in questo caso il tempo di penalit diventa diverso, la formula che
permette di calcolare il tempo di penalit quindi riconducibile a questo ragionamento.
MissRate dell'hard disk*(colpi di clock hard disk -memoria centrale) + Missrate memoria centrale*(colpi di
clock memoriacentrale-cache).
Quanto vale il numero di colpi di clock dall'hard disk alla memoria centrale? Non facile da calcolare perch
il tempo di accesso all'hard disk non noto, dipende da dove si trova la testina, su quale pista si trova il
dato.

124

2. Cache dal punto di vista tecnologico


Vediamo ora dal punto di vista tecnologico come fatta una memoria.
Abbiamo un esempio di memoria dinamica e una statica.
2.1. Memoria statica SRAM
C una linea sulla quale si legge un bit (BL), e una sulla quale si legge lo
stesso bit negato (BL negato),e infine c la linea di parola(word line, WL)
che porta il valore in uscita. La cella statica ha un vantaggio, fin quando c
alimentazione in grado di mantenere le informazioni per un tempo
teoricamente infinto.
Questo tipo di cella utilizzato per le memoria cache, poich fornisce
elevate velocit anche se occupa pi spazio. La sua realizzazione richiede 6
transistor per un singolo bit.
2.2. Memoria dinamica DRAM
In quella dinamica al secondo capo c un condensatore, se il bit 0 il condensatore scarico, altrimenti
carico. Questa cella per non autoalimentata perch il condensatore si scarica, da cui il nome dinamica
perch nel tempo varia il suo valore. Il condensatore che pian
piano si scarica comporta che linformazione verr mantenuta
solo per un certo periodo, il periodo di scarica del condensatore.
Affinch la cella dinamica sia utile necessario che possa fare
un'operazione di refresh. Si legge tutto quello che sta scritto sulla
riga, se c 0 non si esegue nulla, se c il valore 1, questo viene
riscritto nella riga azzerando il tempo di scarica del
condensatore(refresh).
Allora si pu organizzare un meccanismo in cui, letta una riga la si riscrive, letta l'altra la si riscrive ecc.
Si utilizza la cella dinamica perch dal punto di vista dei componenti richiede solo un condensatore ed un
transistor per bit, a differenza di quella statica che ne richiede 6 per bit.
Quindi il sistema realizzato con solo un condensatore ed un transistor, per il refresh rallenta il circuito.
Quando si effettua il refresh la memoria non pu essere letta/scritta.
Tipicamente, il dispositivo statico occupa pi spazio ma pi veloce, viene utilizzato per realizzare una
memoria cache, mentre se c la necessit di pi capienza, si punta su dispositivi di tipo dinamico.

3. Dimensionamento della memoria dal punto di vista del tempo


Adesso cerchiamo di dimensionare una memoria cache dal punto di vista del tempo di accesso e quindi di
gestire il progetto in termini di dimensioni dei blocchi e numero di blocchi.
E' intuitivo pensare che pi grande il blocco maggiore la penalizzazione di fallimento, perch se voglio
125

sapere quanto ci vuole per prendere il blocco e copiarlo dalla memoria centrale alla cache, una volta che ho
indirizzato la memoria centrale devo perdere pi tempo quanto pi grande la dimensione del blocco,
quindi un tempo crescente che non passa da 0, perch un minimo lo si deve pagare in termini di tempo
anche se la dimensione del blocco piccolissima.
Vediamo ora il missrate come si comporta in funzione della dimensione del blocco. Aumentando la
dimensione del blocco il miss rate diminuisce bruscamente. Perch aumentando la dimensione del blocco
possibile caricare pi cose nella cache e quindi fallire meno volte. Sembrerebbe quindi che aumentando la
dimensione del blocco il miss rate diminuisca. Per aumentando ancora la dimensione del blocco, il numero
di blocchi che possibile caricare nella cache si riduce, la cache sta ospitando blocchi troppo grossi, e quindi
caricando l'altro blocco si va a sovrascrivere un blocco che scritto prima, in seguito il blocco sovrascritto che
servir non essendo pi in cache generer un fallimento.
La dimensione del blocco ad esempio potrebbe cadere intorno ai 17.3 byte, ma non si pu fare un blocco di
questa dimensione, perch innanzitutto la dimensione deve essere di byte interi e poi questi byte devono
anche essere un multiplo di parole. Se ad esempio ci sono parole di 4 byte bisogna scegliere la dimensione
del blocco di 16 byte o di 20 byte, ma consigliabile anche che i byte siano multipli di potenze di due per
quanto riguarda la suddivisione dellindirizzo nelle parti che individuano etichetta, set ecc.
Compromesso dimensione blocco non troppo piccola per non caricare di volta in volta un byte ma neanche
troppo grande per evitare che dopo la carica di due blocchi la memoria diventi gi satura.
-

Aumentando lassociativit diminuiscono i conflitti per aumenta il costo perch aumenta il numero
dei blocchi indirizzabili e quindi aumenta il numero delle etichette. Le etichette diventato pi
lunghe, e aumenta il numero di comparatori, bisogna cercare tra le possibili etichette il contenuto
uguale a quello desiderato. La ricerca va fatta in simultaneo e quindi tutti i blocchi vanno letti
contemporaneamente. La politica di sostituzione pi costosa. Se la cache molto grande non ci
sono grandi benefici.

Maggiore dimensione di blocchi, aumenta il tempo di trasferimento. Per in una memoria a


dimensione stabilita il numero dei blocchi diventa di meno e il numero delle etichette diminuisce e
diminuisce anche la lunghezza delletichetta.

Politica LRU: lhardware ha un costo maggiore, diminuisce il miss rate. La politica LRU ha senso per
cache a basso livello di associativit.

Buffer di scrittura: maggior costo hardware (il buffer hardware) e diminuisce lo stallo di scrittura.
Aumento il buffer di scrittura la cache si occupa di scrivere in memoria centrale e gli stalli
diminuiscono.

126

CAPITOLO 15

STRUTTURA ELETTRONICA DELLA CACHE

Analizziamo il processore Motorola 68040, in cui cache dati e la cache istruzioni sono ovviamente separate
ed entrambe hanno la stessa dimensione di 4KB. La cache set associative a 4 vie, e ogni blocco
comprende 4 parole da 4 byte.
Un blocco ha dimensione pari a 4parole *4byte = 16byte. In un set ci sono 4 blocchi(4 vie) per un totale di
64byte per set. Ci sono dunque 4KB/64byte = 64 set.
Dal punto di vista della gestione delle etichette esiste un bit di validit per ogni blocco. Quando si scrive un
blocco in un set, si analizza il bit di validit, se questo zero il blocco vuoto. Se tutti e 4 i blocchi hanno bit
di validit 0 sono tutti e 4 liberi. Il change bit invece uno per ogni parola del blocco. Quando si lavora in
write back (questa memoria lavora sia in write back che in write through) e bisogna sovrascrivere un
blocco, prima di farlo bisogna verificare se il bit di modifica pari a 1 o 0. Se vale 1 allora si trascrive nella
memoria centrale il valore della cache, altrimenti si sovrascrive il blocco nella cache senza aggiornare nella
memoria il valore, visto che la cache e la memoria centrale contengono lo stesso dato, esiste una coerenza
tra la cache e la memoria centrale. Per semplificare lalgoritmo di scrittura quindi si pu sovrascrivere un
blocco che non stato modificato: se c un set pieno e ci sono 3 blocchi modificati e uno no, si pu
decidere di sovrascrivere quello non modificato cos non bisogna aggiornare la memoria centrale. Il sistema
operativo configura la cache a lavorare sia in modalit write back e sia in write through.
La memoria si progetta a partire dallindirizzo. Un indirizzo costituito da 32 bit. Questo non dipende dalla
cache, se dipendesse dalla cache sarebbero necessari 12 bit, 4KB = 212. Il processore vede per una
memoria di 32 bit, indirizza 4GB = 232. Per il processore esiste una sola memoria di 4GB. Il processore
dunque invia questo indirizzo di 32 bit, che pu essere un istruzione o un dato. Il blocco di 4 parole di 4
byte, per un totale di 16 byte. Quindi per indirizzare una parola allinterno del blocco sono necessari 4 bit
(24 = 16). Questi sono i 4 bit meno significativo dellindirizzo. Di questi 4 bit gli ultimi due sono uguali a zero
visto che le parole sono di 4 byte (multipli di 4).
Dei rimanenti bit, gli n meno significativi servono per indicare il set. Poich ci sono 64 set, sono necessari 6
bit (log264=6).
I restanti 22 bit indicano letichetta.

La cache riceve dal processore lindirizzo, che spacchetta a partire dal bit meno significativo: i 4 bit meno
significativi indirizzano la parola nel blocco. Ma quale blocco? Quello che si trova nel set indirizzato dagli

127

altri 6 bit meno significativi. Ma quale blocco nel set, visto che ce ne sono 4? Quello con letichetta uguale
al campo etichetta dellindirizzo. Bisogna per verificare che il blocco abbia il bit di validit uguale a 1.
1. Analisi elettronica della cache
Il processore invia lindirizzo alla porta ingressi della cache il quale viene copiato in un buffer.
Gli ultimi 4 bit dellindirizzo sono inviati alle porte indirizzi di tutti i blocchi. In parallelo tutti i blocchi di tutti
i set ottengono questo indirizzo sulla loro porta indirizzi. Se si sta effettuando un operazione di lettura, alla
porta di controllo della memoria arriva il segnale di lettura (R/W), tutti i blocchi cercano la parola dato
lindirizzo che hanno ricevuto alla loro porta indirizzi. Quindi tutti questi blocchi in simultaneo si attivano
per decodificare e indirizzare la parola a partire dallindirizzo dato.
Mentre i blocchi decodificano, avviene lindividuazione del set a partire dai restanti 6 bit del campo set
dellindirizzo. Ora solo le etichette dei 4 blocchi di quel set appena individuato verranno mandati nei
comparatori. Ci sono 4 comparatori, ognuno per ogni etichetta, che ricevono ad una porta letichetta di un
blocco, allaltra porta i 22 bit dellindirizzo che identificano letichetta. Luscita del comparatore va in
ingresso alla porta AND insieme al bit di validit del blocco. Solo se la porta AND restituisce 1 allora
abbiamo determinato il blocco. In questo caso quindi solo una porta AND determiner un valore pari a 1, e
questo verr retro azionato ad attivare la lettura del blocco del set associato, e quindi di tutti i blocchi che si
erano predisposti ad inviare la parola, solo uno, questo identificato, la invier al processore.
Se dalluscita delle porte AND non arriva nessun valore 1 si determinato il page-fault della cache. In
questultimo caso si stalla il processore e si cerca il blocco dalla memoria sottostante.
Lo stesso vale nella fase di scrittura. Il dato arriva sulla porta dati, per soltanto un blocco abilitato a
ricevere il dato da scrivere.
Una memoria un po diversa quella del Power-PC, ha una cache di 16KB, le parole nel blocco sono 8, e il
numero del set raddoppiato. Lassociativit resta a 4 via e anche la dimensione delle parole resta la
stessa. Cambia la filosofia di gestione del validity bit e change bit: ci sono 2 bit aggregati per blocco, questi
due sono una parola di stato(macchina a stato). E un sistema con 4 possibili configurazioni, 2 stati
rappresentato il change bit e il validity bit, gli altri 2 stati per gestire situazioni di condivisione dati con altre
cache dedicati ad altri processori. Questa memoria non usa un bit per ogni parola perch essendo il blocco
grande il doppio rispetto allaltra memoria mettere 8 bit di modifica diventa pi fastidioso. Con questa
memoria cambia la formattazione dellindirizzo: i 5 bit meno significativi vengono utilizzati per indirizzare la
parola nel blocco(essendo un blocco di 8 parole da 4byte,23*22 = 25e gli ultimi due bit di questi valgono
zero) . Dei restanti, i meno significativi indirizzano il set, essendoci 128 set, sono necessari 7 bit. Letichetta
composta da 20 bit.
Vediamo cosa succede dal punto di vista elettronico nella cache quando il processore invia un indirizzo alla
cache. Bisogna innanzitutto individuare il set, e comparare le etichette dei blocchi di quel set con il valore
che la cache ha ricevuto dal processore, che lei ha staccato dallindirizzo per la ricerca della presenza o
meno del blocco in cache. Avendo, ad esempio, blocchi di 16 byte e 32 set (25 set), lindirizzo composto da
4 di bit per indirizzare la parola nel blocco, 5 bit per indirizzare i set e i restanti 23 per letichetta.

128

Tutte le etichette di tutti i blocchi di tutti i set devono essere collegati ai quattro comparatori. Le quattro
etichette di un set devono essere tutte collegate ad un comparatore diverso. Quindi ad un comparatore
sono collegate tante etichette quanti sono i set, tutte etichette di set diversi. Quindi possiamo supporre che
al primo comparatore siano collegati tutti i blocchi 0 di tutti i set, allaltro comparatore tutti i blocchi 1,
allaltro comparatore tutti i blocchi 3 e allultimo comparatore tutti i blocchi 4. Ad ogni comparatore
dunque sono collegati 32 etichette, ma solo una di questa potr entrare nel comparatore. In particolare
tutte le etichette arrivano in un multiplexer posto davanti allingresso del comparatore, e solo una di
queste etichette andr in ingresso effettivamente al comparatore. Questo mux necessita di un ulteriore
ingresso, il selettore che indica quale dei 32 ingressi dovr uscire. Il selettore quindi composto da log232 =
5 bit, proprio quanti sono i bit dellindirizzo necessari a indirizzare il set. Quindi il selettore rappresentato
proprio dal campo set dellindirizzo, e indica quale etichetta inviare nel comparatore.
Allaltro ingresso del comparatore c letichetta dellindirizzo che va in parallelo allingresso di tutti i
comparatori.
Luscita del comparatore va in una porta AND con il bit di validit del
blocco collegato allingresso del comparatore. Posso pensare che al
Mux arrivi letichetta completa, cio quella composta dai 23bit che
indicano il blocco pi 1 bit, il bit di validit.

129

La parte delletichetta composta da 23 bit va nel comparatore, mentre il bit di validit va in ingresso alla
porta AND con il risultato del comparatore.
Al termine si ottengono 4 segnali, si possono avere solo due tipi di configurazioni: o tutti 0 oppure tre 0 e
un solo 1. Questi quattro segnali possono andare in input ad una porta OR. Se in uscita di questa OR c 1,
significa che c la pagina, altrimenti si genera page-fault.

Se viene generato page-fault, la cache lo spedisce al processore, e il sistema operativo attiva il gestore della
memoria per caricare la pagina in cache dalla memoria centrale.
Nel frattempo i 4 bit dellindirizzo, che indirizzano la parola nel blocco, vanno alla porta indirizzi di tutti i
blocchi, che erano gi pronti a leggere o scrivere quella parola. Di tutti questi blocchi solamente uno
restituir la parola in uscita, tutti gli altri avranno lavorato invano, speculativamente.
Ogni blocco oltre alla porta indirizzi avr una porta di enable(o chip
select), il quale se vale 1 dice al blocco di lavorare se vale zero di non
fare nulla. Lingresso di enable gestito dai 4 bit che si ottengono in
uscita alle 4 porte AND. Questi 4 bit entrano in un demultiplexer alla
cui porta di uscita ci saranno 32*4 linee dove scorrono i bit, che saranno mandati a tutti i set. Solo il set
indicato dal selettore avr la configurazione di bit pari a quella in ingresso al demux, le altre configurazioni
sugli altri set invece saranno nulle. Ogni linea di queste 4 ,che arrivano su ogni set, sono collegate in ordine
ai blocchi, cio la prima linea al blocco 1, la seconda al blocco 2 e cos via. Saranno collegati precisamente
alle porte enable di ogni blocco. Quindi di tutti i blocchi solo 1, del particolare set, avr ricevuto 1 sulla
porta enable, e sar quello che dovr lavorare.

130

Nel disegno non rappresentato, ma tutti i blocchi sono collegati in parallelo alla porta di output della
cache(collegamento three-state). Solo quello che ha ricevuto questo segnale enable scriver la parola che
ha decodificato, e quindi questo sar luscita della porta di output. In fase di scrittura avviene il contrario:
sulla porta dati di input c il dato e i blocchi congelano questo dato e lo scriveranno solo se arriva lenable.
Facciamo lesempio in cui il la parola si trova nel blocco 1 del set 2 e questo blocco ha bit di validit 1: la
configurazione in uscita alle porte AND 0100, questa configurazione viene retro azionata, giunge al de
multiplexer che regolato sempre dai bit del campo set dellindirizzo imposter la pista di 4 bit che arriva a
questo set con la configurazione dei bit dellingresso mentre tutti gli altri bit a zero. Poich questi bit di
uscita sono ordinatamente collegati alle porte enable dei blocchi, ricever 1 solo il blocco 1 del set 2.

131

2. Struttura delle memorie


Le celle di memoria sono aggregate in una matrice. Bisogna conoscere quindi la parole, 32 bit per esempio.
Questi 32 bit indirizzano la parola, ci vuol dire che in ingresso e in uscita da questa memoria ci sono 4
byte. La memoria per semplicit supponiamo sia di 4KB(212), questo significa 1024 parole da 4 byte. Le
memorie sono organizzate in matrici quadrate: 1024 parole, 210, che suddividiamo in 25 righe e 25 colonne.
Quindi la memoria costituita da 1024 caselle di 4byte, e organizzate in 32 righe e 32 colonne.
La memoria riceve un indirizzo di 12bit, visto che per indirizzare una memoria di 4KB (212) sono necessari 12
bit. Se questa memoria indirizzata alla parola, gli ultimi n bit sono nulli, con n = log2(parola). Nel nostro
caso gli ultimi due bit dellindirizzo sono zero. I restanti 10 bit vengono divisi in 2 parti da 5 bit: 5 bit
indirizzano una riga e 5 bit indirizzano una colonna. Un decodificatore un circuito logico che riceve n bit in
ingresso, restituisce 2n uscite. Con 5 bit si indica un numero e luscita del decodificatore individua solo la
linea, che pu essere una riga o una colonna, e questa si illumina. Decodificata sia la riga e la colonna si
determinata la cella.(il comando di un sottomarino nucleare, per spedire il razzo atomico, server la chiave
del comandante e la chiave del vice, se non mettono entrambi la chiave il missile non parte!)
Per mettere in moto una cella bisogna decodificare 5 bit per la riga e 5 bit per la colonna. Queste
decodifiche possono essere fatte simultaneamente, e quindi la latenza per decodificare la riga pari alla
latenza per decodificare la colonna. Un decodificatore di 5 bit pi veloce di un decodificatore a 8 bit ed
anche pi semplice da realizzare visto che ha solo 25 uscite. Se la matrice fosse stata rettangolare, 28x22,la
latenza comandata dal decodificatore pi lento, quindi 28. Visto che sono sempre 4KB di memoria, ha pi
senso rendere la matrice quadrata.

132

Esercizi
1) Data una memoria cache set associativa di 4MB e spazio di indirizzamento 4GB. La memoria a 8 vie, il
blocco di 8 parole da 8 byte. Definire come composto lindirizzo.
Svolgimento:
spazio di indirizzamento 4GB = 232 , n = log2 4GB = 32 bit. Lindirizzo composto da 32bit.

32bit

Di questi 32 bit, x bit meno significativi indirizzano la parola allinterno del blocco. In ogni blocco ci sono 8
parole da 8 byte, per un totale di 8*8byte = 64byte per blocco. Per indirizzare il blocco sono necessari
log264 = 6bit.
x

Indirizzo della parola nel


blocco

Di questi 6 bit una parte pari a zero, la parte meno significativa: log28byte = 3.
I restanti 32-6 = 26 bit vanno divisi in due parti, una parte per indirizzare il set, e un'altra parte per
letichetta.
I set sono indirizzati con i bit meno significativi. Bisogna conoscere il numero di set. Essendo la cache di
4MB e sapendo che ogni blocco di 64byte, ci sono 4MB/64byte = 222/26=216 blocchi. Essendo la memoria a
8 vie, cio ogni set composto da 8 blocchi, n blocchi/8 = n. set : 2 16/23=213 set. Per indirizzare 213 set sono
necessari 13 bit.

Etichetta(13 bit)

Indirizzo del set (13bit)

I restanti 32-13-6 = 13 bit sono per letichetta

2) Data una memoria cache ad indirizzamento diretto di 8 MB, con spazio di indirizzamento a 4GB con 128
blocchi. Definire come composto lindirizzo.
Svolgimento:
Indirizzo da 32bit, considerando uno spazio di indirizzamento di 4GB.

32bit

133

Essendo a indirizzamento diretto(set associativa a 1 via), significa che ci sono 128 set con un solo blocco,
ogni blocco(o set) ha dimensione 8MB/128 = 223/27=216 byte. Sono necessari 16 bit, i meno significativi, per
indirizzare la parola nel blocco. Di questi 16bit non posso dire se gli ultimi bit sono nulli, perch non
conosco la dimensione della parola. Dei restanti, essendo 128 set, sono necessari 7 bit, i meno significativi,
per indirizzare i set (o blocchi). I restanti 32-16-6 ) 9 bit sono destinati alletichetta.

etichetta (9bit)

indirizzo del blocco (7bit)

indirizzo parola nel blocco (16bit)

3) Data una cache completamente associativa da 1MB, 1024 blocchi e spazio di indirizzamento 4GB.
Svolgimento:
Lindirizzo sempre di 32bit poich lo spazio di indirizzamento di 4GB.
Ogni blocco ha dimensione pari a 1MB/1024 = 220/210 = 210 byte, sono dunque necessari 10bit per
indirizzare la parola nel blocco.
Essendo la memoria completamente associativa, i restanti bit, 32-10=22bit identificano tutti letichetta.

etichetta (22bit)

indirizzo parola nel blocco(10bit)

134

CAPITOLO 16

DIVERSE ARCHITETTURE DI MEMORIE


Vediamo qualcosa dal punto di vista dell'architettura che pu migliorare le prestazioni delle memorie. Nel
precedente capitolo abbiamo gi visto il meccanismo di indirizzamento all'interno di una memoria a
semiconduttore: data la memoria di n locazioni, con parole da 4 byte per esempio, abbiamo un indirizzo
che composto da log2 n bit, che hanno la particolarit di avere gli x BIT meno significativi uguali a 0, dove x
pari a log2 N, con N pari al numero di byte della parola indirizzata, 4 in questo caso.
Adesso vediamo come avviene l'indirizzamento. Dagli n bit, tolti gli x bit che non sono significativi dal punto
di vista dell'indirizzo, poich tutti 0, diciamo che m=n-x bit di indirizzo sono effettivi. Presi m/2 ed m/2 si
pu impostare una matrice quadrata di 2(m/2) righe e 2(m/2) colonne, e quindi si ha un meccanismo per
indirizzare una riga e una colonna per identificare una parola fatta da 2x byte.

Una volta presa la parola questa verr instradata su una porta che conterr x byte, o viceversa se una
scrittura la porta riceve il dato e va a collocarsi nella parola localizzata.
Dal punto di vista delle prestazioni, in un certo tempo T, verranno fuori x byte(oppure ricever in scrittura
altrettanti x byte). Questo il tempo necessario per fare lo step 1, decodifica dell'indirizzo e abilitazione
delle porte che devono ricevere o spedire il dato. Quindi gli m/2 bit e m/2 bit che provengono dalla porta
indirizzo della memoria, sono codificati con un codificatore che ha m/2 bit in ingresso e 2^m/2 linee in
uscita per abilitare una fra le 2m/2 righe, gli altri m/2 bit sempre con il meccanismo del codificatore abilitano
una colonna. Il tempo di decodifica dipende dal numero di bit che si decodificano, pi sono gli ingressi e pi
complicata la decodifica. Le operazioni di decodifica della riga e delle colonne, sono simultanee, hanno
la stessa latenza, visto che sono m/2 ed m/2, stiamo codificando due oggetti identici. Questo significa che a
al tempo di decodifica bisogna aggiungere il tempo necessario affinch il segnale di abilitazione si propaghi
lungo la riga e lungo la colonna. Verr sollecitata l'allocazione di memoria che avr una sua latenza di
lettura o scrittura (che sommariamente sono valori di tensione) e questi valori letti vanno spediti in uscita.
La somma di questi tempi: decodifica, invio, abilitazione, lettura e trasferimento, da il tempo in cui questa
memoria pu essere letta o scritta. In questo tempo otteniamo la lettura o la scrittura di x byte.
Dal punto di vista della velocit si parla della velocit a banda, o meglio il rapporto tra quantit di dati che
vengono letti (o scritti) e il tempo impiegato per leggerli (o scriverli), quindi il rapporto tra i dati da operare,
e il tempo con cui avviene loperazione:

135

Se in un certo tempo vengono letti x byte, si ha una certa banda. Se nello stesso tempo se ne leggono il
doppio, si ha una banda doppia, si effettua loperazione due volte pi velocemente. Possiamo riformulare
meglio dicendo che la banda pari al rapporto fra la dimensione dei dati da operare e il tempo necessario a
operare su questi dati:
Significa che se si vuole aumentare la banda, o si aumentano i dati letti nello stesso periodo o si diminuisce
il tempo per leggere la stessa quantit di dati.
Come possibile aumentare la banda?
1. Aumentare la banda aumentando i dati letti o scritti: banchi paralleli
Supponiamo di avere realizzato un chip, dove si
ha un certo traffico in un determinato tempo t,
ad esempio 1s e con x pari a 1 un byte, quindi
la banda sar b = x/t = 1byte/1s, 1MB/s.
Ora, supponiamo di voler quadruplicare questa
banda. Analizziamo l'indirizzo del byte,
ovviamente, se sia sta indirizzando il singolo
byte, l'indirizzo tutto significativo, non si
hanno bit meno significativi nulli. Quindi tutti i
bit dell'indirizzo sono tutti utili.
Come si possono aumentare i dati letti in un
periodo? Il chip che progettato prima, cio
quello che ricevuto lindirizzo tira fuori un byte al tempo t, viene copiato per tante volte quanto si vuole
replicare la banda, ad esempio quattro volte se si vuole quadruplicare la banda, ottenendo cos il banco
parallelo. Avendo preso 4 moduli uguali, la memoria aumentata di un fattore 4, quindi se prima la
memoria era grande 1MB, per esempio, quindi con un indirizzo di 20bit (2 20=1MB), adesso, avendone
copiati 4 si ha una memoria di 4MB, con un indirizzo che diventato di 22 bit (2 22=4MB). Quadruplicare la
banda significa che in un accesso, in un tempo t si deve tirare fuori non pi una parole di 1 byte, ma una
parola di 4 byte.
Quindi in ingresso a questo nuovo chip andranno 22 bit, essendo ora la parola di 4 byte, riguardo l'indirizzo
i 2 bit meno significativi saranno sicuramente nulli. I 20 bit dellindirizzo, ottenuti dai 22bit meno i 2 bit
nulli, vengono inviati in parallelo a tutti i 4 blocchi. Quindi tornando a quanto detto prima, ognuno di quei
quattro blocchi vuole un indirizzo di 20 bit e tira fuori un byte. Quei 20 bit che vengono inviati a ognuno di
quei quattro moduli indirizza una parola di 1 byte che viene mandata fuori. Sommando tutte le parole
ottenute dai quattro moduli si ottiene una parola formata da, 4*8bit = 32bit, 4byte.

136

I 20 bit in ingresso a ognuno dei 4 moduli, supponiamo indichino riga 5 e colonna 8, indirizzeranno 1 byte in
ognuno dei 4 moduli. Quindi ogni modulo tirer fuori la parola situata alla riga 5 e colonna 8. Le parole
estratte non sono le stesse, perch il contenuto dei quattro moduli differente, sono indipendenti, mentre
la posizione della parola in ogni blocco la stessa.
Alla fine ho costruito una memoria che tira fuori 4 byte in un tempo t. t era il tempo relativo ad un modulo
necessario a decifrare lindirizzo, abilitare la cella e trasferire la cella. Questo tempo per ogni blocco
rimasto uguale e ogni tempo uguale per ogni blocco. Poich questi quattro moduli lavorano in
simultaneo. Al termine sono stati prodotti il quadruplo dei dati con lo stesso tempo t. La banda si
quadruplicata.
2. Aumentare la banda diminuendo il tempo: banchi interlacciati
Data una certa memoria che in un t tira fuori un certo numero di dati, x, la banda dell'oggetto x/t. Ora
bisogna costruire un oggetto che ha una banda superiore andando a lavorare sulla diminuzione della
latenza.
Consideriamo il modulo visto al paragrafo precedente, i banchi paralleli: il modulo riceve 22 bit di cui gli
ultimi 2 sono nulli e tira fuori 4 byte. Adesso bisogna costruire un oggetto a partire da questo che ha una
banda ancora maggiore di quello creato in precedenza.
Quindi se per esempio ci sono 4 oggetti, l'indirizzo di questo macro oggetto avr 24 bit, poich ci sono in
questo caso 4MB*4moduli = 16MB di memoria complessiva, cio il chip dallesterno visto come una
memoria complessiva di 16MB, e sono necessari dunque 24 bit per indirizzare questa memoria, 224 = 16MB.
La dimensione della parola resta la stessa, che resta sempre di 4 byte. Questo comporta che dei 24 bit gli
ultimi 2 sono uguali a 0. Questo indirizzo inviato entra nell'oggetto, e ogni modulo che lo compone
necessita di soli 22 bit, di cui gli ultimi due devono ancora essere nulli. Come vengono scelti questi 22 bit da
inviare a questi moduli?
137

Dei 24 bit si compongono i 20bit pi significativi con i due bit nulli. I bit meno significativi subito dopo la
coppia di 0 in realt servono ad indicare la parola. Ci sono delle previsioni che si possono fare sui prossimi
byte che serviranno, ad esempio si possono ipotizzare che successivamente si dovr prelevare la parola
successiva a quella appena richiesta. Questa previsione quanto probabile? Dipende dal livello di memoria
in cui ci si trova. Se ad esempio ci si trova nella memoria centrale, si considera un blocco con tutti i suoi
dati annessi, in questo caso siamo sicuri che se dalla memoria centrale si sta leggendo il primo dato di un
blocco, si vuole sicuramente leggere il secondo dato del blocco. Quindi, con un blocco di 4 parole, e arriva
un certo indirizzo per prelevare la prima parola del blocco, questo indirizzo avr i 2 penultimi bit meno
significativi nulli, e sicuramente, dopo la prima lettura, si ricever un indirizzo per prelevare la seconda
parola del blocco, e i 2 penultimi meno significativi nulli saranno 01 ecc. Poich stiamo nel livello tra la
cache e la memoria centrale, questi dialogano a blocchi, di conseguenza quando si chiede un dato, una
parola, alla memoria centrale, questa invier alla cache lintero blocco. Ipotizzando che il blocco sia
composto da 4 parole, quei due bit andranno proprio a identificare la parola.

I 22 bit, indirizzeranno un determinato modulo, quel modulo in un tempo t tirer fuori 4 byte, se si
necessitano di altri 4 byte, ci vorr un altro tempo t. A questo punto, la memoria sembra sia andata veloce
come la memoria precedente, ma a me serve anche tirare fuori le altre parole del blocco. Allora si invia
anche agli altri moduli lindirizzo di 22bit e la prima memoria tira fuori la prima parola, la seconda memoria
tirer fuori la seconda parola e cos via, al termine del tempo t tutte le memorie avranno estratto le parole.
A questo punto, il grosso del lavoro, dal punto di vista della latenza, stato gi fatto. Le 4 parole lette
contemporaneamente, saranno congelate e si tirer fuori la prima parola, dovuta allindirizzo composta
dalla coppia dei due penultimi bit meno significativi pari a 00 nel caso della parola 1. Al secondo colpo di
clock, quando verr richiesta la seconda parola, quindi quei due penultimi bit meno significativi saranno 01
e ora verr fatta uscire la seconda parola che era stata congelata precedentemente. Quindi questi due bit
vengono utilizzati per controllare un selettore, perch visto che al primo colpo di clock stata chiesta una
parola, ma nel frattempo per anche le altre componenti hanno tirato fuori le loro parole, questo decide
quale delle parole andr in uscita. Quindi per prelevare queste parole necessario un tempo T, poi la prima
parola uscir con il tempo necessario ad attraversare il selettore. Al secondo colpo di clock il tempo per
ottenere la seconda parola, sar solo quello necessario per attraversare il selettore, visto che stata
prodotta al colpo di clock precedente. Alla termine si hanno 4 parole in un tempo t+4T, dove T il tempo di
attraversamento del selettore, e t il solito tempo dovuto alla decodifica dellindirizzo abilitazione della
cella e uscita della cella. Nota bene: questo un discorso che non speculativo, non migliori le cose solo
138

nel caso in cui il processore vuole la parola successiva, se questa architettura la metto su una memoria che
consultata per blocchi, questo vantaggio si ottiene sempre.

Ritornando alla struttura delle memorie precedente, lindirizzo spacchettato in 2 moduli, uno per le righe e
uno per le colonne, l'incrocio di questi due segnali abilitano una riga e una colonna. La parola accetter in
ingresso il dato o lo trasferir.
Un oggetto del genere ha un problema: con una memoria di 1MB si utilizzano 20 piedini. In un sistema in
genere si cerca di diminuire il numero di piedini. Infatti il pin ha una dimensione difficilmente migliorabile
con la tecnologia.
Come diminuire il numero di piedini per una memoria?
Diminuendo il numero di piedini sulla porta dati, proporzionalmente si diminuisce la banda della memoria,
se si serializza la parola(o se ne diminuisco le dimensioni), si ha una latenza che il tempo per leggere la
parola pi i colpi per leggere un bit alla volta. Quindi conviene diminuire il numero della piedinatura
nell'indirizzo, ad esempio potremmo dividere la dimensione dell'indirizzo, e utilizzare solo la parte
realmente significativa. Supponiamo di lavorare con 10 piedini che ricevono 10 bit, questo comporta che
l'indirizzo di 20 bit deve arrivare in 2 colpi di clock, in un colpi do clock i primi 10 bit e al secondo colpo di
clock gli altri 10. In questo modo per sembra che sia necessario il doppio del tempo, in realt pure
mettendo la met dei piedini, non si impiega il doppio del tempo. Con 10 bit alla volta, non ha senso avere
un decodificatore, che riceve 10 bit e abilita una delle 10 linee, non bisogna lavorare in parallelo con righe e
colonne. Allora inutile un secondo decodificatore, arriveranno prima i bit della riga e si abilita la riga, poi
arrivano i bit della colonna e si abilita la colonna, tutto questo usando un solo decodificatore.

139

Quindi dimezzando il numero di piedini


decodificatore.

comporta un ulteriore risparmio, o meglio si risparmia un

Vediamo per un'altra soluzione. Supponiamo di aver interrogato una memoria per avere un certo byte,
quindi forniremo le 2 parti dell'indirizzo alla memoria e arriver il dato. Se siamo a livello di memoria
centrale, il prossimo byte sar quello successivo. Quindi il chip sa che subito dopo la parola che sta
caricando ora, dovr caricare la prossima, che si trova affianco a quella appena caricata, questo un fatto
certo, perch la memoria centrale lavora con i blocchi. Quando devo tirar fuori la parola seguente, i 20 bit
di indirizzo di quella parola sono sulla stessa riga di quella precedente, per la prima parola quindi codifico
riga e colonna, ma per la seconda la terza e la quarta, codifico solo la colonna, perch la riga sempre
quella. Posso pensare allora che quindi arrivano i primi 10 bit, i pi significativi, che abilitano su un
decodificatore una riga, questa riga tutta trasferita su un buffer, quando arrivano gli altri 10 bit, questi
sollecitano su quel buffer una determinata parola che va in uscita, al prossimo colpo di clock, non indirizzo
la seconda parola con tutti i 20 bit, ma solo con altri 10 bit; in realt posso evitare anche questo con una
logica particolare che scatta di parola con un contatore.
Quindi si dimezzato il numero di piedini dell'indirizzo e fatto si che per il primo dato si abbia una latenza
maggiore, ma per tutti quelli successivi, la latenza sempre quella precedente se non migliore perch vado
a leggere il buffer dove stata trascritta la riga. Quindi, tanto pi grande il blocco, tanto meno
percettibile il dazio pagato per aver spezzato in due l'indirizzo da inviare.

140

CAPITOLO 17

POLITICHE DI GESTIONE DELLE MEMORIE


Consideriamo le diverse politiche di gestione della memoria analizzando quello che avviene con un sistema
composto da una CPU con la sua cache e la memoria centrale e linterazione Input/Output o con pi
processori.
1. Interazione con i dispositivi di I/O
Nellimmagine le linee tratteggiate indicano la connessione tra le periferiche di I/O e il sistema. I vari livelli
su cui possibile questa comunicazione sono: direttamente con la CPU, direttamente con la cache,
direttamente con la memoria centrale, o a livello di bus tra CPU e cache o tra cache e memoria centrale.

Ci sono una serie di dispositivi di input/output connessi tra di loro su un bus di I/O collegati con un
adattatore al resto del sistema. Lobbiettivo capire dove attaccare ladattatore di input/output.
Se si collega direttamente alla CPU con un operazione di input questo da dei problemi per mantenere
coerente tutto quello che c nelle memorie sottostanti, cache e memoria centrale. Se loperazione non
sincrona, per sincrono si intende qualcosa che avviene in un determinato istante e quellistante sempre
lo stesso. Un fenomeno, un evento avviene sempre in un determinato istante; ad esempio quando si chiede
il valore di x in un programma e finch il valore di x non stato dato il programma non va avanti
nellesecuzione, quellevento sincrono, cio finch non stato fornito il valore di x, a quella linea di codice
il programma attende sempre che venga fornito il valore di x. Se invece c un sistema che monitora
lambiente con dei sensori che ricevono quello che avviene, poi trasducono questo e inviano al sistema dei
segnali opportuni, questo un evento non sincrono. Si controlla un forno di cottura di pandori quando la
temperatura arriva oltre una soglia si spegne la corrente che attraversa la resistenza del forno, allora non si
141

sa quando questo segnale arriva e si gestisce questo fenomeno. Un evento non sincrono quando si invia
alla stampante lavvio di una stampa, quando terminano i fogli la stampante invia un segnale che avviene in
maniera inaspettata. In questi casi la CPU dovrebbe perdere tempo a gestire ci che avviene nellI/O.
Se invece avviene a livello di memoria centrale, caricato il dato x in cache, quando c un aggiornamento di
x in memoria centrale, il processore vede in cache il dato non aggiornato. Questo vale anche per loutput,
se il valore di x in cache e il processore ha aggiornato il valore di x, in memoria centrale c il valore
vecchio che viene anche inviato in output. Il dialogo in memoria centrale da problemi sia in operazioni di
input e sia in operazioni di output, in cui la memoria centrale trasferisce in output valori del dato non
aggiornati. Questo secondo problema non esiste se la cache di tipo write through, poich ogni volta che i
dati vengono modificati dal processori nella cache questi sono aggiornati anche in memoria centrale, e in
output c il valore corretto. Per se considerazione la situazione in cui nella cache c il dato x e con un
operazione di input viene aggiornato x in memoria centrale, c il problema che la cache continua ad
utilizzare il valore vecchio di x, quello non aggiornato, poich era gi presente in cache.
Se invece si dialoga a livello di cache, i problemi descritti prima non ci sono. Con un operazioni di input si
scrive nella cache, con loperazione di output si legge dalla cache. Quando si scrive il dato x in cache, si
modifica anche il change bit in quel blocco cos da scrivere il valore aggiornato anche in memoria centrale.
In questo modo per si ottiene un sistema coerente per
c il problema che la cache dovr dialogare con lI/O
quando il processore lo permetter, e inoltre la cache
chiamata a far transitare al suo interno anche i blocchi dei
dati su cui si fanno operazioni di input e output. Parte
della cache occupata da blocchi interessati dalle
operazioni di input e output e il processore utilizza risorse
minori, ha a disposizione meno linee della cache. Il
processore che ha una cache di K KB di cui il 90%
occupato dalle operazione di input output. Si satura la cache con blocchi che il processore non ha mai
referenziato.
Per cui collegando lI/O direttamente alla cache, vero che si ha coerenza ma come se la cache fosse
molto pi piccola e si paga sempre miss rate.
Ritorniamo al sistema in cui I/O dialogano con la memoria centrale. La gestione dellI/O gestita dalla
memoria centrale. Attraverso il meccanismo di write through come abbiamo gi detto si presentano dei
problemi durante le operazioni di input, perch se x era gi in cache, e al successivo aggiornamento di x, il
processore ha il valore scaduto. Bisogna gestire questo sistema attraverso due filosofie che partono da un
principio, non ammissibile che se copiato x in cache e poi questo viene modificato in memoria centrale,
non ammissibile che in cache ci sia un valore non coerente, bisogna evitare che questo avvenga.
Fondamentalmente bisogna far si che la cache che il valore di x gi caricato al suo interno non ha pi valore.
Per far si che la cache si accorga che il valore non pi idoneo bisogna far si che la cache curiosi nella
memoria centrale, si faccia i fatti della memoria centrale. Deve vedere se lI/O sulla memoria centrale
modifichi il valore di x. Quando linput scrive in memoria centrale, quello che viene scritto deve essere
visibile anche alla cache. Ladattatore dialoga con la memoria centrale anche su un canale ascoltato dalla
memoria cache, in modo tale che se si sta avvenendo linput di x in memoria centrale, la cache se ne

142

accorge dellarrivo del nuovo valore di x. Questo meccanismo prende il nome di Snoopy cache, si dice
anche che la cache deve sniffare i dati che arrivano dal canale di I/O. Si vede che in input c il valore della
variabile x vede anche che in suo blocco c il valore di x e si accorge che il valore di x al suo interno non ha
pi significato perch arriva un nuovo valore dal canale di input. Come fa a capire che i bit sul canale di
input sono i bit che codificano il canale di x? Quando si effettua un operazione di I/O c un canale dati e un
canale indirizzo. In sostanza la cache prende lindirizzo dal canale indirizzi ne estrae la parte che presente
nelletichetta di un determinato blocco e fa la stessa operazione che farebbe quando la CPU comanda di
scrivere un dato in un certo indirizzo. Quindi arrivato il dato dal bus dati, corrisponde come indirizzo a un
dato che stato gi caricato, allora questa una situazione critica che si risolve con due politiche di snoopy:
1. Accortosi che il dato un aggiornamento del dato al suo interno, allora il dato al suo interno non
vale pi niente. La cache invalida il blocco, tutto il blocco anche se laggiornamento riguarda solo x,
perch quel blocco non ha pi senso. Se la cache strutturata con un validity bit per ogni parola,
allora si invalida solo quella word che contiene il valore di x. Questo al costo di avere tanti bit di
validit. In questo se il processore chiede quel valore, essendo il validity bit settato a zero a indicare
che quel blocco(o quella parola) non valida, si copia il blocco dalla memoria centrale, con il valore
aggiornato.
2. Il dato presente in cache, arriva un aggiornamento, invece di invalidarlo, lo copi direttamente.
Arriva il dato e la cache se lo prende. Potrebbe sembrare la stessa situazione di quando si dialoga
direttamente con la cache, ma non cos. Ora si prende il dato soltanto quando questo un
aggiornamento di quello che in cache. Questa politica sembra migliore della precedente, perch
ora quando il processore chiede il dato x, questo gi presente in cache non bisogna copiarlo dalla
memoria centrale. Questo meccanismo per blocca la cache per aggiornare un valore che in
cache, e nessuno dice che quel valore verr utilizzato dal processore. Pu darsi che quel dato al
processore non serva pi e si perso tempo sulla cache, il processore si stallato per un attimo.
Invalidando il blocco e poi il processore non chiede pi x risultata un operazione intelligente
invalidarlo.
Quindi per realizzare questo meccanismo di Snoopy cache, il collegamento con lI/O va a livello di bus
tra cache e memoria centrale.

143

2. Interazione con pi processori


Un problema analogo sussiste in un sistema multiprocessore, un sistema con pi processori, ciascuno di
questi ha una cache interna, una cache esterna con un meccanismo di memorie distribuite o centrali.

Le CPU dialogano con le loro cache, e le cache dialogano o con le memorie distribuite o con la memoria
centrale, che non ha problemi di scambiare dati, perch se questi processori si vogliono scambiare dati
questi lo fanno scrivendo i dati nella memoria centrale. Per far si che questi non siano mondi paralleli e
separati si deve pensare a un meccanismo di connessione tra le memorie distribuite. Con questo
sistema c il vantaggio di avere lazione privilegiata di ciascun processore con la sua memoria e dialoga
con la banda della sua memoria destinata alla sola CPU, e lo stesso vale per tutte le CPU. Bisogna per
prevedere che queste memorie si scambino tra loro queste informazioni.
Questo modello si semplifica utilizzando una sola memoria centrale senza le memorie distribuite. Le
cache dialogano con la memoria centrale. La memoria centrale che ha una certa banda, questa banda
se la deve dividere sui pi sistemi. Ogni cache non pu prendersi tutta la banda altrimenti laltra cache
dovr stallare per fare anchessa delle operazioni. In ogni caso a prescindere questo sistema prevede
dei dati scaduti se un dato x viene caricato in cache e viene modificato dal processore. Se la cache
write through il dato viene aggiornato anche nella memoria centrale. Se prima di questo
aggiornamento il dato x stato prelevato da un altro processore, questultimo processore avr un dato
144

scaduto. Anche in questo caso bisogna prevedere un meccanismo di memorie Snoopy con politiche
simile a quelle viste precedentemente. La scrittura in memoria centrale deve essere ascoltata da tutte
le memorie cache. Quando si scrive tutte le cache devono accorgersi di quello che avviene. Il discorso
pi semplice con una memoria centrale non distribuita, con politiche analoghe a quelle viste prima. Per
loutput non c problema, per le operazioni di input bisogna invece pensare a meccanismi di Snoopy
cache, invalidando il dato in cache o copiando il dato direttamente in cache.

145

146

CAPITOLO 18

TECNICHE DI COMPILAZIONE EFFICIENTI


Nellesecuzione di un programma sorgono problemi quando un istruzione deve stallare poich utilizza un
dato che destinazione di un'altra istruzione precedente, e questa non lo ha ancora prodotto. Basti
pensare al processore floating point in cui una moltiplicazione o una divisione durano diversi colpi di clock.
Schedulando le istruzioni in modo appropriato, come gi visto, si riescono a eliminare degli stalli. Trovare
per un certo numero di istruzioni che spostate, senza sconvolgere lesecuzione del programma,
rimpiazzino gli stalli non sempre semplice da fare. Quello che succede che se ci sono diversi stalli da
coprire questa operazione potrebbe essere impossibile. Se ci sono cinque istruzioni e ci sono 12 stalli, non
possibile coprire tutti questi stalli. Allora si utilizzando tecniche di compilazione come lo srotolamento del
loop o la pipeline da programma.
1. Srotolamento del loop
Lo srotolamento del loop (loop unrolling) consiste nel replicare il corpo di un ciclo pi volte modificando
opportunamente il codice.
Consideriamo il seguente programma strutturato in un loop, che esegue la moltiplicazione di due vettori
C=A*B:
1. loop:
2.
3.
4.
5.
6.
7.
8.
9.

L.D F1,(R7)0
L.D F2,(R8)0
MUL.D F3,F1,F2
S.D F3,(R9)0
DADDI R7,(R7)8
DADDI R8,(R8)8
DADDI R9,(R9)8
DADDI R2,(R2)-1
BNEZ R2, loop

Si possono schedulare le istruzioni per evitare degli stalli per ci accorgiamo che la moltiplicazione floating
point necessitando di 12 colpi di clock, ad esempio, per produrre il risultato comporta che non possibile
coprire il numero degli stalli con le istruzioni a disposizione.
Quello che viene in mente il cosi detto srotolamento del loop. si scrivono le istruzioni che compongono il
loop per un certo numero di volte. Si replicano ad esempio per quattro volte. Il loop dovr fare in questo
caso N/4 iterazioni, visto che si srotolato il loop per quattro volte. In questo modo si paga un numero
eccessivo di istruzioni, quindi si paga in termini di memoria, visto che alcune istruzioni si scrivono per
quattro volte.

147

Lesempio precedente considerando lo srotolamento di quattro iterazioni diventa:


1. loop:
L.D F1,(R7)0
2.
L.D F2,(R8)0
3.
MUL.D F3,F1,F2
4.
S.D
//------------------------ primo srotolamento
5.
L.D F4,(R7)8
6.
L.D F5,(R8)8
7.
MUL.D F6,F4,F5
8.
S.D
//------------------------ secondo srotolamento
9.
L.D F7,(R7)16
10.
L.D F8,(R8)16
11.
MUL.D F9,F7,F8
12.
S.D
//------------------------ terzo srotolamento
13.
L.D F10,(R7)24
14.
L.D F11,(R8)24
15.
MUL.D F12,F10,F11
16.
S.D
//------------------------ quarto srotolamento
17.
DADDI R7,(R7)32
18.
DADDI R8,(R8)32
19.
DADDI R9,(R9)32
20.
DADDI R2,(R2)-4
21.
BNEZ R2, loop

F3,(R9)0

F6,(R9)8

F9,(R9)16

F12,(R9)24

Il loop stato srotolato, replicando un corpo del loop per quattro volte. Non tutte le istruzioni sono stati
replicate, quelle relative alla gestione del loop, decremento del contatore e branch, non sono state
replicate. In questo modo ci sono pi istruzioni da guardare nel loop e quindi da utilizzare per riempire gli
stalli. Tipicamente si sistemano le istruzioni in modo omogeneo, mettendo tutte le operazioni dello stesso
tipo di seguito, prima tutte le operazioni load, poi tutte le operazioni di calcolo e poi tutte quelle di
memorizzazione, una di seguito allaltra. Questo modo di schedulare le istruzioni evita gli stalli.

1. loop:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

L.D F1,(R7)0
L.D F4,(R7)8
L.D F7,(R7)16
L.D F10,(R7)24
L.D F2,(R8)0
L.D F5,(R8)8
L.D F8,(R8)16
L.D F11,(R8)24
MUL.D F3,F1,F2
MUL.D F6,F4,F5
MUL.D F9,F7,F8
MUL.D F12,F10,F11
148

13.
14.
15.
16.
17.
18.
19.
20.
21.

S.D F3,(R9)0
S.D F6,(R9)8
S.D F9,(R9)16
S.D F12,(R9)24
DADDI R7,(R7)32
DADDI R8,(R8)32
DADDI R2,(R2)-4
BNEZ R2,loop
DADDI R9,(R9)32

Srotolando in maniera opportune il codice , si riescono a evitare gli stalli. Si paga un codice pi lungo, quindi
si spreca pi memoria per scrivere questo codice, ma si paga anche un numero eccessivo di registri, si
occupano pi registri. Il costo di questa tecnica riassunto in un numero di registri pi grande, quattro
volte il numero di registri e di una dimensione del codice, quasi quattro volte di pi, quasi perch non tutte
le istruzioni sono state replicate come abbiamo detto prima. Per il programma pi veloce visto che sono
stati evitati gli stalli.
2. Pipeline da programma
Un'altra tecnica di compilazione la pipeline da programma.
Supponiamo di avere il programma visto nel paragrafo precedente, C=A*B, e strutturiamo il loop con
questa logica:
Loop:
-

carica A(i)
carica B(i)
C(i-1)<- A(i-1)*B(i-1)
scrivi C(i-2)

Nella moltiplicazione si utilizzano i dati caricati alliterazione precedente, e non si dovr stallare per
attendere che la load produca A(i) e B(i), poich i dati sono stati gi prodotti. La store scrive invece il
risultato prodotto due iterazioni precedenti in modo tale che la moltiplicazione abbia prodotto il risultato.
Gestire un loop in questo modo, evita di saturare i registri, evita di riempire la memoria con le istruzioni. Si
ha il solo problema di gestire questi dati in maniera opportuna.
Il nome di questa tecnica prende il nome di pipeline da programma poich nel programma si viene a creare
una sorta di pipeline, perch c uno stadio che riguarda i, uno stadio che riguarda i-1 e un altro stadio che
riguarda i-2.
Sorge un problema, al momento della prima iterazione la moltiplicazione non avr A(i-1) e B(i-1) poich
stiamo alla prima iterazione, e cos anche la store che necessita addirittura degli elementi di due iterazioni
precedenti. Bisogna quindi predisporre un transitorio di riempimento, in cui si caricano i primi elementi su
cui operare nel loop.

149

Transitorio di riempimento:
-

carica A(0)
carica B(0)
carica A(1)
carica B(1)
C(0)<- A(0)*B(0)

a questo codice pu seguire il loop strutturato precedentemente. Se il vettore va da 0 a N (vettore di N+1


elementi), il loop inizia con i=2, e termina con i=N. Al loop deve seguire un transitorio di svuotamento per
operare sui restanti elementi caricati e a cui non stata effettuata loperazione di moltiplicazione o non
sono stati ancora scritti in memoria.
Transitorio di svuotamento:
-

C(N)<-A(N)*B(N)
scrivi C(N-1)
scrivi C(N)

In conclusione il programma ha una struttura del genere:


Transitorio di riempimento:
-

carica A(0)
carica B(0)
carica A(1)
carica B(1)
C(0)<- A(0)*B(0)

Loop:
-

carica A(i)
carica B(i)
C(i-1)<- A(i-1)*B(i-1)
scrivi C(i-2)

Transitorio di svuotamento:
-

C(N)<-A(N)*B(N)
scrivi C(N-1)
scrivi C(N)

Il codice del programma visto prima diventa:


//transitorio di riempimento
1.
DADDI R2,(R0)2
2.
DADD R1,R0,R6 //R1<- N dimensione vettore
3.
L.D F1,(R7)0
150

4.
5.
6.
7.
8.
9.

L.D F2,(R8)0
L.D F3,(R7)8
L.D F4,(R8)8
MUL.D F5,F1,F2
DADDI R7,(R7)16
DADDI R8,(R8)16
// loop
10.
S.D F5,(R9)0
11.
MUL.D F5,F3,F4
12.
L.D F3,(R7)0
13.
L.D F4,(R8),0
14.
DADDI R7,(R7)8
15.
DADDI R8,(R8)8
16.
DADDI R2,(R2)1
17.
BNEQ R2,R1, loop
18.
DADDI R9,(R9)8
//transitorio di svuotamento
19.
MUL.D F10,F3,F4
20.
S.D F5,(R9)0
21.
S.D F10,(R9)8
I transitori si bilanciano, ci sono 4 load(le load sono maggiori delle store), 2 moltiplicazioni, 2 store. Nel
transitorio di svuotamento non ci si preoccupa delle load.

151

152

CAPITOLO 19

PARALLELISMO A LIVELLO DI ISTRUZIONI


Cerchiamo di vedere un approccio a un tipo di architettura che cerca di risolvere il problema legato al fatto
che nel calcolatore ci sono una serie di moduli che non detto che siano sempre impegnati durante
lesecuzione di una istruzione. Pensiamo ad un processore floating point a cui, come sappiamo, alla tipica
architettura del calcolatore, sono stati aggiunti una serie di moduli indipendenti per il calcolo floating point,
e questi non che detto che siano usati tutti in ogni istruzioni, poich si possono avere istruzioni di accesso
a memoria piuttosto che di uso dellALU e cos via. Avere questi moduli nel sistema inutilizzati, suggerisce la
possibilit di pensare a una struttura che effettivamente cerchi di usarli.
1. Processore VLIW
Viene interessante questo approccio nel momento in cui c la capacit di accedere a memoria con una
banda passante tale da prelevare in un colpo di clock un istruzione pi lunga dei consueti 32 bit. Questo
approccio si chiama VLIW, Very Long Istruction Word. Listruzione rappresentata da una parola molto
lunga. necessaria la capacita di accedere ad una memoria con una banda passante pi elevata.
Supponendo di avere una memoria istruzioni capace di essere interrogata su un canale che consente la
lettura in un colpo di clock per esempio di 128 bit piuttosto di 32 bit, allora si pu pensare di codificare un
istruzione non pi usando 32bit ma bens 128 bit. chiaro che riuscire a prelevare un istruzione di 128bit in
un colpo di clock, ha senso se si riesce a eseguire simultaneamente queste istruzioni che compongono la
VLIW di 128bit. Per semplicit consideriamo 4 istruzioni classiche compattate in una istruzione che le
raggruppi tutte e 4. Poter accedere alla memoria istruzioni e prelevare in un colpo di clock tutte queste 4
istruzioni, consente al sistema, se questo capace, di eseguire in simultanea queste 4 istruzioni. Questo
diverso dal concetto di pipeline dove le n istruzioni sono eseguite simultaneamente ma non parallelamente.
Per esecuzione parallela si intende che ogni istruzione eseguita simultaneamente alle altre. Avendole
prelevate tutte insieme parte la loro vita nello stesso instante.
32

32

32

32

Per questo approccio si ha la necessit di un processore che abbia una banda di memoria istruzione
maggiore della solita. Inoltre bisogna compattare quattro istruzioni classiche che possono essere eseguite
in simultaneo senza recare problemi.
Consideriamo una sequenza di istruzioni come questa:
A=B+C
D=A*B
Queste due istruzioni non possono essere eseguite contemporaneamente. Per avere un approccio VLIW
necessario che non ci sono conflitti di dato. Quindi aver prelevato tutte insieme le istruzioni consente di
poter pensare di eseguirle simultaneamente. Inoltre, non solo deve essere soddisfatta lindipendenza dei
dati, ma bisogna far si che le istruzioni che compongono la VLIW richiedano delle unit funzionali tutte
diverse, bisogna evitare quindi i conflitti strutturali. Bisogna avere dei vincoli sui dati che non devono creare
153

dipendenze sui dati della stessa istruzione e dei vincoli strutturali , cio i moduli richiesti da quelle istruzioni
non devono essere gli stessi o se sono gli stessi bisogna aver provvisto il processore di una certa
ridondanza(duplicare alcuni moduli). Per cui chiaro che se non si vogliono pagare pesanti ridondanze,
bisogna imporre dei vincoli sulle possibili operazioni che possono essere eseguite simultaneamente. Questo
avviene attribuendo alle istruzioni che compongono la VLIW dei campi ben precisi e quindi in un modello in
cui si suppone una VLIW di 160 bit pari a contenere 5 istruzione di base, si pu supporre che le prime due
siano istruzioni di accesso a memoria dati, poi ce ne siano due che richiedano luso di moduli floating point
e poi ce ne sia una che richieda lALU oppure che possa gestire i moduli di branch per fare una
sovrascrittura del PC.
MEM1

MEM2

FP1

FP2

ALU (o branch)

In questo caso si deciso di pagare una memoria dati che abbia una banda doppia rispetto al solito, visto
che si sono due riferimenti a memoria; delle unit floating point che sono indipendenti, di conseguenza
possiamo avere un istruzione che utilizza il sommatore e un'altra che utilizza il moltiplicatore, a meno che
non si decida di inserire due moltiplicatore e quindi di eseguire nella VLIW due istruzioni di moltiplicazione.
Non volendo raddoppiare lALU in questa istruzione di questo genere si ha la possibilit di avere una
istruzione che effettivamente adoperi lALU . Le istruzioni che fanno riferimento a memoria, per il calcolo
dellindirizzo non possono utilizzare lALU poich eseguita in simultanea dallistruzione ALU. Si dispone
quindi di un hardware aggiuntivo per il calcolo dei due indirizzi per non replicare lALU 2 volte, e utilizzare 3
ALU. Fortunatamente gli indirizzi dove andare in memoria derivano sempre da un operazione che una
somma, un registro pi un immediato e quindi ovviamente. Volendo avviare in parallelo queste istruzioni,
poich questi i primi due campi sono dedicati a moduli per accedere a memoria, opportuno far si che il
calcolo di questi indirizzi avvenga in maniera indipendente.

Che cosa succede se il compilatore non trova nella traduzione da


linguaggio alto livello a livello macchina, un numero di istruzioni
congruo per riempire i campi di una VLIW? In sostanza il
compilatore dato il programma, ed essendo in VLIW, comincia a
prendere 5 di queste istruzioni che compongono il programma e le
va ad impaccare in una istruzione VLIW, poi ne prende altre 5 e le
impacco in una istruzione VLIW e cos via. Per fare questo, queste 5
istruzioni devono essere coerenti con il formato della macchina,
devono rispettare le condizioni che abbiamo detto prima.
E chiaro che trovare cinque istruzioni che soddisfino le condizioni imposte dalla VLIW non detto che ci
siano. Allora alcuni di questi campi della VLIW che rimangono scoperti verranno codificati con delle
istruzioni che prendono il nome di NOP , not operation.
Quindi se non ci sono cinque istruzioni da inserire nella VLIW, quelle che non ci sono vengono sostituite con
una NOP. Per chiaro che quando in un programma, per esempio di 10 istruzioni, e queste 10
compattandole in VLIW diventano non due VLIW, formate ognuna da 5 istruzioni, ma diventano 10 perch
queste 10 sono tutte istruzioni ALU, cio in ogni VLIW c solo un istruzione ALU e tutto il resto NOP, ed
essendoci 10 istruzioni ALU si realizzano 10 VLIW, allora si sta sprecando questa potenzialit. Si possono
fare contemporaneamente 5 istruzioni per tra queste 4 non fanno nulla.
154

E evidente che questo approccio, VLIW, lavori molto con tecniche come quella dello srotolamento del loop,
in maniera tale che avendolo srotolato il loop per un certo numero di volte si hanno a disposizione un certo
numero di istruzioni da compattare in VLIW; al massimo non saranno tutti e 5 i campi del VLIW ad essere
riempiti ma almeno 3 o 4 saranno riempiti. Per cui per immaginare di riempire efficacemente una VLIW
bisogna spesso e volentieri partire dal codice srotolato.
Quindi in questo esempio noi vediamo una scrittura srotolata di un codice che fa essenzialmente carica gli
elementi di un vettore a cui somma uno scalare e poi aggiorna in memoria il nuovo valore dellelemento.

In sostanza questo programma carica un vettore di double , fa la lettura degli elementi, e ad ogni elemento
va a sommarci lo scalare F2 e poi aggiorna il vettore con il risultato. Il loop stato srotolato 7 volte, per cui
in una iterazione si caricano 7 elementi del vettore che vengono messi in 7 registri differenti, che vanno poi
a essere sommati a F2 a produrre i risultati da aggiornare in memoria. Si gestisce il loop usando il puntatore
R1 decrementandolo di 56 locazioni perch nello stesso loop R1 viene decrementato da 0 a 8 a 16 a 24 a 48
locazioni per cui il prossimo dato che si dovr prelevare come primo della prossima iterazione deve essere
spostato 56 locazioni rispetto al valore attuale di R1. Quindi questo decremento interesser il registro R1
155

con limmediato 56. Listruzione di salto che finch R1 non uguale a 0 salta al loop, quindi ritorna alla
prima istruzione , e infine c lultima store , quella che interessa lultimo dato prodotto (F28). La store di
F28 stata posta dopo la bnez il solito trucco per non avere la penalit di salto. Quella bnez verr codificata
con un codice operativo che porta a termine listruzione schedulata dopo di lei sempre, sia se il salto si
effettua e sia se non si effettua, va sempre portata a termine. Per spostare la store dopo la bnez, e quindi
necessariamente dopo loperazione di decremento di R1, fa si che lindirizzo della store non sia -48 R1,
come dovrebbe giustamente essere, ma con R1 spostato di 56 locazioni in avanti, si sottrae allimmediato 8
il valore di R1, cio 56, ad ottenere lindirizzo a -48.
Ora bisogna prendere queste istruzioni e inserirle in un approccio VLIW, prendere 5 istruzioni e impaccarle
in un'unica istruzione VLIW. Nella tabella seguente ogni riga rappresenta una singola istruzione VLIW.
MEM1
L.D F0,0(R1)
L.D F10,-16(R1)
L.D F18,-32(R1)
L.D F26,-48(R1)
NOP
S.D F4,0(R1)
S.D F12,-16(R1)
S.D F20,24(R1)
S.D F28,8(R1)

MEM2
L.D F6,-8(R1)
L.D F14,-24(R1)
L.D F22,-40(R1)
NOP
NOP
S.D F8,-8(R1)
S.D F16,-24(R1)
S.D F24,16(R1)
NOP

FP1
NOP
NOP
ADD.D F4,F0,F2
ADD.D F12,F10,F2
ADD.D F20,F18,F2
ADD.D F28,F26,F2
NOP
NOP
NOP

FP2
NOP
NOP
ADD.D F8,F6,F2
ADD.D F16,F14,F2
ADD.D F24,F22,F2
NOP
NOP
NOP
NOP

ALU(o branch)
NOP
NOP
NOP
NOP
NOP
NOP
DADDIU R1,R1,#-56
NOP
BNE R1,R2,loop

Ci sono 9 istruzioni VLIW, per 9 colpi di clock. In totale vengono eseguite 23 istruzioni in 9 colpi di clock, con
una frequenza di emissione di 2.5 operazioni per colpi di clock.
La prima istruzione utilizza due istruzioni di accesso a memoria e poi avrebbe spazio per caricare due
istruzioni FP e un istruzione ALU. Dopo aver caricato F0 e F6, nelle altre due istruzioni floating point avrei
potuto fare la somma di F0 e F2 in F4 e F6 e F2 in F8, ma avrei utilizzato i valori di F0 e F6 che cerano fino a
quel momento, non quelli appena caricati. Per utilizzare effettivamente F0 e F6 bisogner aspettare che le
due LOAD li abbia prodotti, cio dopo la fase di MEM dellistruzione LOAD, in LMD. Quindi luso effettivo di
F0 e F6, attraverso la corto circuitazione dellALU con LMD, avviene dopo due colpi di clock. I campi della
VLIW prive di istruzioni floating point o ALU saranno NOP. Listruzione VLIW ha sempre 160bit, per i campi
3,4 e 5 saranno codificati ad indicare che non c nulla da fare.
Nella seconda istruzione VLIW, ci sono due istruzioni che fanno accesso a memoria, mentre per le
operazioni floating point c il problema di prima. Non possibile ancora utilizzare F0 e F6, poich non sono
stati ancora prodotti. Allora anche qui si codificano queste operazioni con NOP.
Nella terza essendo ancora possibile effettuare accessi a memoria, si piazzano nei primi due campi ancora
istruzioni che fanno accesso a memoria. Ora nei campi floating point possibile inserire le operazioni di
addizione, poich sia F0 e sia F6 sono usciti dalla memoria. La memoria ricordiamo essere a doppia banda.
Nel campo di istruzione ALU non possibile ancora effettuare nessuna operazione, perch effettuare qui la
sottrazione del contatore o effettuare il branch sconvolgerebbe il programma.
Alla quarta istruzione VLIW c lultima delle 7 LOAD da inserire nel primo campo di riferimento a memoria.
Nel secondo campo si potrebbe pensare di inserire una STORE, la store di F4 o la store di F8, per F4 e F8 in
156

quel punto non sono pronti, perch si sta effettuando un operazione ADD floating point che richiede un
certo numero di colpi di clock per produrre il risultato. Si possono eseguire le store a partire dalla sesta
istruzione.
Nella quinta istruzione seppure non si potuto fare uso delle istruzioni di accesso a memoria, perch non
stato ancora prodotto nulla da poter scrivere con la store, comunque si possono eseguire i calcoli e quindi
altre due ADD. Se il loop fosse stato srotolato per 10 volte invece che 7, in MEM1 e MEM2 ci sarebbero
ancora 3 load da inserire al posto delle NOP. Attenzione che srotolare il loop per 10 volte comporta un
maggiore utilizzo di registri.
Completate le ADD si possono finalmente effettuare le STORE.
Particolare attenzione bisogna fare sul fatto che dopo la BNEZ si pagher uno stallo. Questa andrebbe
messa come penultima istruzione, listruzione DSUB andrebbe posta allistruzione 6, con opportune
modifiche agli offset per il calcolo degli indirizzi nelle store.
2. Processore superscalare
Un approccio pi semplice quello della macchina superscalare.
La macchina superscalare parte dallidea come la macchina VLIW che nel processore ci sono una serie di
unit, e alcune di queste interessano solo alcune istruzioni. Nei processori floating point si aggiunto
dellhardware per effettuare appunto le operazioni floating point, chiaro che un istruzione e interessata
a fare la fase di EXE nellALU o interessata a fare la fase di EXE in floating point.
Listruzione nel processore pipeline fluir o nella parte floating point o nella parte di unit intera.
Se si riesce ad avere un programma nel cui codice c un alternanza di istruzioni intere e istruzioni floating
point e se si riesce a fare si che la memoria istruzioni in un colpo di clock legga due istruzioni alla volta,
allora verranno prelevate in un colpo di clock simultaneamente due istruzioni: una intera e una floating
point e queste due andranno ad interessare una la parte di unit intera e laltra invece la parte sullunit
floating point. Si realizza una VLIW a due istruzioni.

Occorre un unit di controllo pi sofisticata della macchina normale, e una memoria con una banda doppia,
che permette di leggere due istruzioni in un colpo di clock.
Al primo colpo di clock vengono prelevate in simultaneo due istruzioni: un istruzione ALU e una floating
point visto che il codice del programma stato schedulato in modo da avere un alternanza di istruzioni ad

157

aritmetica intera e istruzioni floating point. Queste due istruzioni vengono eseguite contemporaneamente
una sullunit intera e una sullunit floating point, e cos via.
Questo meccanismo funziona nel momento in cui il compilatore ha tradotto il codice in modo che le
istruzioni si alternano una intera e una floating point. Se nel codice questo non possibile, perch non ci
sono istruzioni ALU o sono meno delle istruzioni floating point, oppure perch sono bilanciate ma nascono
conflitti di dato nello strutturarli in alternanza (cosa poco probabile perch istruzioni floating point e intere
sono indipendenti tra loro a livello di registri) allora la macchina prelever due istruzioni, le codificher si
accorger che sono entrambe istruzioni intere, ad esempio, non le potr eseguire in simultaneo, una
attender che laltra termini.

158

CAPITOLO 20

PROCESSORE VETTORIALE
Nel capitolo precedente abbiamo analizzato due tipi di processore, il processore VLIW e il processore
superscalare. In questo capitolo focalizzeremo lattenzione sul processore vettoriale.
Consideriamo il calcolo vettoriale, il calcolo che riguarda i vettori. Il calcolo vettoriale che interessa il
processore riguarda solo alcune delle operazioni sui vettori. Le operazioni vettoriali che interessano il
processore vettoriale hanno questa caratteristica:
-

Sono composte da operazioni scalari


Sono tra di loro indipendenti

Consideriamo la somma di due vettori che produce un vettore i cui componenti sono dati dalla somma degli
elementi dei primi due. La media di un vettore un operazione che crea dipendenze, perch al primo
elemento va sommato il secondo e cos via per poi dividere per il numero degli elementi.
Quando non c dipendenza tra le singole operazioni scalari che compongono la macro istruzione vettoriale
si crea uno scenario interessante. Fare la somma di due vettori interessante in quanto la somma di vettori
di per se una somma che contiene al suo interno 64 somme di scalari che non hanno dipendenze di dati,
perch si effettua la somma del primo elemento del primo vettore con il primo elemento del secondo
vettore, e questo non influisce sulla seconda somma.
Con il processore vettoriale si ha la possibilit di codificare con una sola istruzione un calcolo notevole.
Durante la somma di due vettori di 64 elementi, c la necessit di gestire in un loop di 64 iterazioni queste
somme insieme anche alle istruzioni di gestione del loop, incremento dellindice, istruzione salto e cos via.
Si pu inventare un istruzione, la somma, per la somma di vettori, ad esempio, che sia la somma di 64
coppie di dati. Non una VLIW di 64 campi, una semplice istruzione somma, somma vettoriale. Con una
singola istruzione da 32 bit si codifica tutto quello che era necessario fare con N istruzioni.
add scalare: ADD.D
add vettoriale: ADDV
Listruzione codifica le 64 istruzioni scalari. Queste 64 istruzioni per non devono avere conflitti di dato tra
loro. Per cui ammettendo che ci sono somme floating point che nella fase EXE durino un certo numero di
colpi di clock, essendo indipendenti, si possono strutturare le unit di calcolo in maniera pipeline. E
listruzione non deve per forza terminare prima di avviare la seconda, ma nel moltiplicatore possibile
inserire altre coppie di elementi. E dopo che viene prodotto il primo risultato dal sommatore, ad esempio
dopo un certo numero di colpi di clock, i successivi risultati escono dal sommatore una dopo ogni colpo di
clock.
Nasce lesigenza di creare un architettura che ha senso se c da fare pensante calcolo vettoriale. Se esiste
una classe di problemi che fa largo uso di calcolo vettoriale preferibile utilizzare un processore vettoriale.
Basti pensare allelaborazione delle immagini, dove si effettuano operazioni che sono le stesse per ogni
pixel dellimmagine. preferibile lavorare sul vettore di pixel, se le operazioni sono indipendenti, cio

159

quello che viene fatto sul primo pixel indipendente da ci che viene fatto sugli altri, e non c bisogno che
venga prodotto il risultato prima che parti loperazione sul secondo elemento.
Per realizzare una struttura del genere necessario quindi che i moduli di calcolo siano pipelineizzati in
modo che quando entra una coppia di elementi a un certo colpo di clock, al successivo ne entri una seconda
coppia, indipendente dal risultato delloperazione della prima coppia.
La potenza di calcolo non stata raddoppiata, si sono resi pipeline i moduli di calcolo.
Per la memoria necessario che con un istruzione LOAD, load vettoriale, in memoria si legga il primo dato e
successivamente i successivi. Non devono essere prelevati subito in un colpo di clock tutti gli elementi del
vettore, il primo elemento deve essere prelevato dopo un certo numero di colpi di clock, latenza della
memoria, e i successivi a seguire uno dopo ogni colpo di clock. La load vettoriale preleva i dati non in
parallelo, ma uno dopo laltro. Appena viene tirato fuori il primo elemento, dopo 3 colpi di clock ad
esempio, al colpo di clock dopo deve essere prelevato laltro elemento e cos via. Questi elementi poi
vengono sommati, per esempio, e come vengono prelevati dalla memoria vanno in ingresso al sommatore,
che produrr il risultato dopo 5 colpi di clock. Una volta prodotti i risultati questi devono essere scritti in
memoria. Per la store non pu cominciare se la load non ha terminato di estrarre gli elementi.
Attraverso le istruzioni vettoriali si eliminano i loop. C una banda memoria istruzioni ottimizzata che
preleva listruzione load vector, LV, e questa implica che bisogna leggere un certo numero di elementi dalla
memoria.
Laccesso a memoria dati, con la load vettoriale, prevede che sia fornito lindirizzo del primo elemento del
vettore dal quale si preleva il primo elemento dalla memoria a quellindirizzo e poi in automatico vengono
generati i successivi indirizzi adiacenti per prelevare gli elementi seguenti. Si pu prevedere una memoria
con banda quadrupla che richieda quattro indirizzi alla volta.
C un modo efficiente di gestire le unit di calcolo e le unit di accesso a memoria dati, e un unit di
controllo che deve supervisionare tutto.
Le operazioni vettoriali lavorano su registri vettoriali, una macro struttura composta ad esempio da 64
registri, 64 locazioni.
Bisogna cercare di dimensionare il numero di elementi dei registri vettoriali. Una volta fissato quel numero,
si possono operare su vettori di quelle dimensioni o pi piccoli. Questo numero non deve essere molto
elevato, ma bisogna pensare come gestire operazioni vettoriali che interessano anche vettori di dimensioni
maggiori. E logico pensare anche che un registro vettoriali da un elemento sia inutile, si parlerebbe di
processori scalari in questo caso. Faremo uso a registri vettoriali di 64 elementi.
Nel corso del capitolo faremo riferimento al processore Cray-1: un supercomputer sviluppato nel 1976 da
un team di progettisti guidati da Seymour Cray per la Cray Research. Il codice assembly proposto non
riferito al MIPS quindi la sintassi potrebbe essere non chiara.
1. Architettura processore vettoriale cray-1
Perch un processore possa lavorare con vettori deve avere larchitettura del processore scalare, a cui
vanno aggiunte una serie di strutture:

160

registri vettoriali
istruzioni vettoriali compilabili ed eseguibili che si aggiungono al set di istruzioni
controllo per accedere ai registri vettoriali e alle memoria
unit funzionali altamente pipelineizzabili
le memorie gestite con un sistema di banchi interlacciati, accedere agli elementi del vettore uno
dietro laltro con un throughtput di uno al colpo di clock
non c gestione di cache e di memoria virtuale

Ci sono 64 registri vettoriali, due di questi accedono in input ai moduli floating point o a virgola intera e
sono i due operandi dellistruzione, poi c laccesso di un risultato.
Il vector mask , vettore di maschera, che ha 64 elementi fatto da 64 flag che indicano quali degli elementi
del vettore realmente devono essere processati, e a quali verr applicata loperazione. Un operazione con
maschera tra V1 e V2 significa che non tutti gli elementi di V1 e V2 devono essere processati, ma soltanto
quelli che hanno nella posizione corrispondente nel vettore maschera il bit di flag uguale a 1. Ad esempio
consideriamo la divisione tra V1 e V2, e bisogna evitare di effettuare la divisione quando gli elementi di V2
sono zeri, allora si imposta un flag dove se lelemento di V2 uguale a zero il flag vale 0, 1 altrimenti e si
effettua loperazione mascherata. Loperazione viene comunque fatta, il fatto che il bit di maschera sia 1 o
0 serve a disabilitare o abilitare la scrittura del risultato. Quindi loperazione con maschera non dura di
meno di un non mascherata, la durata la stessa.

161

Il registro vector lenght (VLR) contiene il numero degli elementi significativi del vettore, che non pu
superare i 64 elementi. Quindi con registri vettoriali da 64 elementi, si possono operare vettori da 30
elementi, impostando vector lenght a 30. Questo introduce un risparmio nella computazione, perch
loperazione si arrester quando verr processato il 30esimo dato del vettore.
Ci sono 16 banchi di memoria da parole di 64bit. Loperazione di fetch riguarda quattro istruzioni in
simultanea, vengono caricate le istruzioni da una memoria con banda di 320mln di parole/secondo e
vengono caricate in quattro istruction register. Il processore ha un clock da 80 megahertz e la memoria tira
fuori una parola ogni 50nanosecondi.
2. Modello di programmazione vettoriale
Si strutturano le operazioni, piuttosto che in loop, in singole sequenze di istruzioni, ciascuna a codificare un
operativit che riguarda tutti gli elementi del vettore, che vanno dallindice 0 allindice memorizzato nel
registro VLR. Il codice si trova ad operare in questo modo: supponendo di voler fare la somma di due
vettori, ADDV v3, v1, v2, dove ADDV ADD vettoriale e gli operandi sono i registri vettoriali; vengono presi
gli elementi di v1 e di v2 fino allindice VLR, e si sommano a completare il vettore v3. Quello che c nelle
altre celle, oltre lindice VLR, non viene interessato. Queste somme inoltre vengono pipelinenizzate.
Vediamo cosa succede nellaccesso a memoria in
caso di Load o Store: la load carica dalla
memoria un registro vettoriale, con stride. Nella
macchina di cray c solo un istruzione load ed
con stride, non esiste LV e LVWS(load con
stride), esiste solo load vector con stride. Lo
stride indica la posizione dellelemento
successivo che deve essere caricato, il passo. Se
non c bisogno di fare salti perch gli elementi
da caricare sono uno di seguito allaltro, lo stride
vale 0 (oppure 8 dipende dalla struttura).
Nellesempio LV v1,r1,r2, r2 rappresenta lo
stride. In r1 c lindirizzo del primo elemento da caricare, poi sommando r2 calcolo gli indirizzi degli altri
elementi che vanno a copiarsi nelle altre celle del registro vettoriale.

162

Confrontiamo ora il codice relativo alla somma di due vettori con un processore scalare e un processore
vettoriale:

Tutto il gruppo di istruzioni del codice scalare, in un codice vettoriale diventano cinque istruzioni vettoriali.
Viene messo 64 in VLR, visto che i vettori sono di 64 elementi. Viene caricato in V1 il vettore il cui indirizzo
in r1, in v2 il vettore che in memoria inizia da r2, e si effettua la somma di questi due vettori in v3. Il vettore
v3 viene memorizzato in memoria a partire dallindirizzo r3.
Non c un loop, queste istruzioni generano un eventuale stallo tra la load e la add. Perch la add deve
aspettare che arrivi il primo elemento di v2, e la store deve aspettare che sia uscito il prim elemento di v3.
Si pu migliorare questa situazione dovuta alla memoria accedendo a v1 e v2 con una memoria con una
banda passante di due dati o pi. Ad esempio un accesso a memoria su tre linee, in modo da effettuare
contemporaneamente una load una load e una store. Con un solo accesso a memoria invece pi
efficiente immaginare il prelievo dei dati delle due load in maniera interscambiabile, cio non prelevare
prima tutto il vettore v1 e poi tutto il vettore v2, ma caricare il primo elemento di v1 poi lasciare da parte
per un momento v1 e prendere il primo elemento di v2 e poi ritornare al secondo elemento di v1 e cos via,
in modo che arrivi sempre una coppia da elaborare al sommatore floating point. Perch funzioni bene il
processore vettoriale opportuno pensare a una memoria con una banda passante utile. Provvedere pi
corsie di accesso a memoria con il meccanismo delle corsie.
3. Vantaggi del processore vettoriale
I vantaggi sono rappresentatoi:
-

Compattezza del codice, molte meno istruzioni


Le operazioni vettoriali del codice codificate in una sola istruzione devono essere indipendenti, non
ci sono conflitti di dato. I dati sono indipendenti.
Tutti i dati utilizzano la stessa unit funzionale, per esempio il sommatore, possibile mettere due
sommatori e quindi dividere le operazioni su due sommatori.
163

Accesso disgiunto ai registri. In simultanea si preleva da v1 e v2 i dati in maniera indipendente nel


senso che si accede ai registri vettoriali indipendentemente da come si accede ad altri registri
vettoriali. Ad esempio quando si esegue in simultanea ADDV v5,v1,v2 e MULV v6,v3,v4, laccesso ai
registri v1 e v2 sar diverso dallaccesso ai registri v3 e v4, visto che i primi vanno in un sommatore,
mentre gli altri in un moltiplicatore, che sar pi lento, quindi anche i risultati saranno prodotti in
maniera diversa. Se non ci sono conflitti fra le unit funzionali i banchi dei registri possono essere
tutti a lavoro.
Accesso ai registri con la stessa struttura con cui si ha avuto accesso a quei registri nellistruzioni
che li ha riguardati. Load con VLR settato a 30, le operazioni su quel dato opereranno con VLR 30 e
le store si fermeranno dopo 30 scritture.
Con stride posto a 1, il caricamento avviene in maniera contigua, si accede al dato vettoriale con la
tecnica analoga a quella dei blocchi della memoria(dialogo memoria centrale-memoria cache).
Con un certo valore di stride, laccesso a memoria non a blocchi ma ha un pattern costante. Se c
una matrice caricata per righe e si vuole un vettore colonna di questa, le colonne distano di tanti
byte quanto larga una colonna. Sono sempre quelli.
Concetto di corsie, lanes. Queste corsie sono dovute al fatto che si aumentata la banda di
memoria, oppure dovute al fatto che stato replicato dellhardware. Se ci sono 4 moltiplicatori, si
ha un accesso a 4 lanes alla batteria di moltiplicatori, e quando si dovr fare una moltiplicazione
vettoriale, i impiegher il quarto del tempo, perch verranno inviati 4 coppie di operandi in
simultaneo.

Consideriamo loperazione V3<- V1*V2, con il modulo moltiplicatore pipelineizzato. Questa operazione
dura 6 colpi di clock. Il primo elemento di V3 verr prodotto dopo 6 colpi di clock, ma gli altri elementi
verranno prodotto uno dopo ogni colpo di clock, facendo entrate in ingresso al moltiplicatore una
coppia di operandi ad ogni colpo di clock.
Analogamente avviene laccesso alla memoria, con 16 banchi di memoria. Per scrivere un registro
vettoriale(store), basteranno 4 colpi di clock pi la latenza di scrittura. Perch al primo colpo di clock si
spediscono in parallelo 16 elementi che si scrivono, gi al secondo vengono altri 16 dati, quindi dopo 4
colpi di clock viene scritto tutto il registro vettoriale. Considerando la latenza di scrittura occorrono in
definitiva 12 colpi di clock. Lindirizzo generato sommando lo stride alla base, quello ottenuto
lindirizzo, lo stesso per tutti i banchi. Al prossimo colpo di clock, questo indirizzo viene retroazionato e
diviene la nuova base, e viene sommato lo stride nuovamente.

164

Consideriamo lesecuzione in pipeline con una sola lane dellistruzione vettoriale ADDV C,A,B:
Sono gi entrati A[0] e B[0] a produrre C[0] che gi uscito dal sommatore, sono
entrati anche A[1] e B[1] che producono C[1], e sono entrati anche A[2] e B[2] per
produrre C[2], e stanno per entrare A[3] e B[3].

Ora vediamo con una pipeline a pi lane listruzione vettoriale ADDV C,A,B:

Ci sono 4 sommatori. Nel primo sommatore vanno A[0] e B[0], nel secondo A[1] e B[1], nel terzo A[2] e
B[2] e nel quarto A[3] e B[3]. Dopo tre colpi di clock sono stati prodotti C[0], C[1], C[2], C[3], e sono
entrati A[4], B[4] nel primo sommatore, A[5] e B[5] nel secondo e cos via. In sostanza invece di avere
una coda di 64 coppie, ci sono 4 code di 16 coppie.
La corsia su cui deve viaggiare ciascuno elemento dato da:
(numero corsia) = (numero dellelemento) mod (numero dei sommatori)
Quindi lelemento A[37] andr nella corsia 37 mod 4 = 1, corsia 1, A[37] andr nella corsia 1.
In sostanza c un sistema che struttura i registri non in un unico blocco, ma in tanti blocchi quanti sono
le lane. Il registro V1 raccolto sulle quattro lanes. Questi sono anche gli elementi cos come vengono
fuori dalle memorie. E ora si parla di banchi paralleli, e non interlacciati, con una banda passante il
quadruplo di quella del singolo banco di memoria.

165

In un processore queste strutture sono organizzare geograficamente.

Gli elementi dei registri sono collocati sulle loro corsie.


4. Processori a registri vettoriali vs processori memory-memory
Unistruzione vettoriale di una macchina memory-memory codifica al suo interno sia laccesso a memoria e
sia luso dellunit funzionale. Ad esempio nella somma vettoriale, vengono indicati gli indirizzi, e questa
istruzione va in memoria a prendere i dati e poi li usa per fare i calcoli.
La macchina a registri invece utilizza operazioni LOAD o STORE per accedere a memoria e caricare o scrivere
i registri, e poi usa lunit funzionale con questi.
Consideriamo il seguente codice:
1. for (i=0; i<N; i++)
2. {
3.
C[i] = A[i] + B[i];
4.
D[i] = A[i] B[i];
5. }
Si traduce in codice per macchina memory-memory in:
1. ADDV C,A,B
2. SUBV D,A,B
Sono istruzioni che accedono direttamente a memoria. Il problema che ci saranno due accessi a memoria.
Si necessita di una banda di memoria maggiore per migliorare le prestazioni ed eseguire in parallelo le due
istruzioni se lhardware non ha conflitti(operazioni diverse), per c il conflitto dellaccesso a
memoria(aumenta la banda passante).
Nella macchina a registri si carica A e B e poi si utilizzeranno V1 e V2 per fare la somma e la sottrazione.
166

Quindi il codice per una macchina a registri diventa:


1.
2.
3.
4.
5.
6.

LV V1, A
LV V2, B
ADDV V3, V1, V2
SV V3, C
SUBV V4, V1, V2
SV V4, D

Il punto di rottura(breakeven point) indica quando diventa efficiente o no lavorare con un processore
vettoriale.
Questo punto di rottura rappresentato dalla dimensione del vettore; perch risulti conveniente lavorare
con un processore vettoriale, visto che questo tipo di processore necessita di un architettura non
indifferente, indispensabile conoscere la dimensione minima del vettore per cui al di sotto del quale
diventa inefficiente lutilizzo del processore vettoriale.
Supponendo di aver un processore vettoriale e di lavorare con vettori di dimensione 1, tutta questa
architettura poco utile, pu creare solo problemi, sarebbe meglio utilizzare un processore memorymemory o processore scalare. Diventa interessante invece lavorare con un processore vettoriale quando si
lavora con vettori di certe dimensioni, in modo che inizialmente si necessita di tempo per riempire le lanes
per dopo il meccanismo abbastanza veloce. Pi basso meglio
Con un processore vettoriale con punto di rottura di 20, ad esempio, ogni qualvolta si lavora con vettori di
dimensione minore di 20, si sta lavorando male con il processore vettoriale. Pi basso e pi
vantaggioso.
Con processore memory-memory con vettori di dimensione minore di 100 elementi vantaggioso lavorare
con processori scalari, con vettori di dimensioni maggiori di 100 ha senso lavorare con processori memorymemory.
Con vettori a registri, necessario che il vettore abbia vettori con almeno due elementi, per avere un
efficienza con questo processore. Effettivamente un vettore con dimensione minore di uno uno scalare.
Dopo luscita del processore vettoriale a registri Cray-1, tutti i processori successivi erano processori
vettoriali con registri.
Nel corso del capitolo non tratteremo processori memory-memory, ma processori vettoriali a registri.
5. Vettorializzazione del codice
Nelle iterazioni c un blocco di codice scalare che si ripete.
Nella vettorializzazzione si compattano le iterazioni in maniera da analizzare la stessa operazione che viene
applicata ai dati diversi, e quella stessa operazione diventa un operazione vettoriale. Tutte le load che
riguardano A[1], A[2], A[3] ecc.. diventano la load vettoriale di A, (LV A). Lo stesso vale per tutte le load che
riguardano B[1], B[2] ecc.. diventano la load vettoriali di B (LV B), cos come add A[1], B[1] add A[2],B[2]
diventano le somme di A e B, ADDV A,B e cos anche per STORE C.

167

6. Vector Stripmining
Una volta stabilita la dimensione massina dei dei registri vettoriali, non detto che quella dimensione sia
ottimale per ogni problema. Pu esserci un problema che richiede lutilizzo di un vettore con dimensione
superiore al numero di elementi del registro vettoriale.
Bisogna immaginare un modo di operare quando il vettore del problema ha una dimensione superiore a
quella del registro vettoriale.
Lidea alla base quella di dividere il vettore in pi parti. Ad esempio trattiamo un vettore di 150 elementi
in 3 pezzi due di 64 elementi e uno di 22 elementi.
Non conviene dividere il vettore in 3 pezzi da 50 elementi, tutti in parti uguali. Questo non va bene perch
dividere in parti uguali comporta una minor efficienza sulle singole operazioni. C un altro problema in cui
non sempre possibile dividere la dimensione del vettore in parti uguali. Quello che importante notare
che la divisione del vettore sar sempre dello stesso tipo: un certo numero di elementi ad ampiezza
massima, 64 elementi, e un residuo, e il residuo il primo ad essere processato, e poi verranno processati i
restanti blocchi da 64. Questo perch supponendo di dover prima processare i pezzi da 64 e poi il residuo,
ogni volta che si processa il pezzo da 64, bisogna chiedersi quello successivo di quanti elementi composto,
se ancora uno da 64 elementi o il residuo? Questo calcolo consiste in una perdita di tempo. Invece se
prima si elabora il residuo e poi si imposta VLR a 64, non ci si preoccupa pi visto che tutti gli altri sono da
64 elementi.
In sostanza si divide la dimensione per 64, e si ottengono i pezzi completi, 150/64 = 2 blocchi completi, poi
il resto determina il residuo, 150 mod 64 = 22.

168

Data la dimensione del vettore, 150 elementi, in un registro, R2 ad esempio.


Dividendo per 64, cio per 26, quindi shiftando verso destra il registro di 6 posizione si ottiene:
(150)10 = (10010110)2
(150/64)10 = 10010110 = (10)2 = (2)10 numero di vettori completi di 64 elementi
I restanti bit, quelli shiftati sono (150 mod 64)10: (010110)2 = (22)10 dimensione del residuo. Questo si pu
ottenere facendo lAND di (150) 10 con (63) 10 (maschera)
(10010110) AND (00111111) = 10110
Un esempio il seguente:

RA contiene lindirizzo del vettore A, RB contiene lindirizzo del vettore B, RC contiene lindirizzo del vettore
C, N la dimensione del vettore.
Attraverso ANDI R1 si ottiene la dimensione del residuo. Impostando VLR pari a R1, cio la dimensione del
residuo, la load di RA in V1, carica i primi VLR elementi, cio quelli del residuo, in V1. R2 invece contiene la
dimensione in byte degli elementi, la dimensione in byte del residuo, e quindi RA si fa puntare poi al primo
elemento del vettore completo subito dopo il residuo. Lo stesso vale per RB, dopo aver memorizzato in V2 i
primi VLR elementi, sempre quelli del residuo. Una volta caricati V1 e V2 si effettua la somma vettoriale in
V3, a partire da RC. Bisogna aggiornare, come RA e RB anche RC, a puntare allelemento successivo su cui
scrivere, perch poi bisogner scrivere a partire da RC.
Residuo
R2
RA

Vettore di 64 elementi

Vettore di 64 elementi
RB

Poi bisogna aggiornare N al valore di N meno il numero di elementi gi processati, R1, che inizialmente vale
il residuo, poi viene settato a 64. N la condizione per terminare il loop. Inoltre viene settato anche VLR a
64 visto che da adesso in poi tutti i vettori saranno di 64 elementi. In VLR inizialmente viene messo il
residuo e dopo invece 64, perch dopo saranno sempre vettori da 64 elementi senza dover chiedere nulla.

169

Come si nota dal codice che con il vettore pi lungo di 64, necessario lutilizzo di un loop. Ogni iterazione
del loop non riguarda un elemento, ogni iterazione riguarda ogni pezzo del vettore, ci sono quindi tante
iterazioni per quanti sono i pezzi cui stato diviso il vettore.
7. Parallelismo delle istruzioni vettoriali
Consideriamo il seguente codice, su vettori da 32 elementi e su 8 lanes:
1. LV V1, R1
2. MULV V3,V1,V2
3. ADDV V5,V3,V4
La load accede alla memoria con 8 corsie. Allistante 0 vengono caricati 8 dati, allistante 1 altri 8, dopo
quattro colpi di clock stato caricato tutto il vettore. Listruzione di moltiplicazione parte, al colpo di clock
2, quando sono stati caricati gi i primi 8 dati e possono quindi gi essere usati in ingresso al moltiplicatore.
Con 8 lanes, ci sono 8 moltiplicatori. Per la somma avviene lo stesso discorso. In sostanza in un colpo di
clock vengono completate 24 operazioni (LV, MULV e ADDV per 8 elementi).

Nellesempio non evidenziata la latenza dei singoli moduli, ad esempio la latenza della memoria per tirare
fuori i dati.
Per effettuare queste operazioni in parallelo, cio cominciare la moltiplicazione non appena sono stati tirati
fuori dalla memoria i primi dati si effettua una corto circuitazione, che conduce i dati appena fuori dalla
memoria verso il moltiplicatore, e i primi risultati prodotti dal moltiplicatore sono cortocircuitati nel
sommatore. Quindi i dati in ingresso al moltiplicatore o sommatore non provengono direttamente dai
registro vettoriali, ma vengono concatenati(chain) attraverso una corto circuitazione di questi nei moduli
appositi.

170

Quindi lesecuzione delle istruzioni in un processore privo del vantaggio del concatenamento, comporta che
bisogna attendere che i risultati di unistruzione sia stati tutti scritti nel registro e poi si pu procedere con
listruzione successiva. Bisogna prima fare tutta la load, poi tutte le moltiplicazioni e poi tutte le somme.

Con un processore con concatenamento invece le istruzioni vengono eseguite non appena listruzione
precedente ha prodotto i primi risultati.

Lesecuzione pipelineizzata:

Fra due istruzioni che utilizzano la stessa unit funzionale viene a crearsi un tempo morto, dovuto al fatto
che quando avviene un cambiamento degli operandi dovuto a un'altra istruzione che utilizza quel moduli, il
registro destinazione deve essere modificato. Se c un moltiplicatore con un certo numero di stadi di
pipeline che fa la moltiplicazione fra 2 registri vettoriali, colpo di clock dopo colpo di clock questo accetta in
ingresso le coppie di operandi e scrive il risultato su un certo registro. Terminata questa operazione,
arrivata lultima coppia di operandi, lui non pu ricevere la coppia di operandi di un'altra istruzione, perch
c una gestione sincronizzata che scrive il risultato in un registro.

171

Consideriamo lesempio in cui si susseguono due operazioni identiche op1 e op2


op1 v3,v1,v2 uscita collegata a v3
op1 v6,v4,v5 uscita collegata a v6

Scritto V3[N] il modulo che effettua loperazione deve cambiare il registro di destinazione dove scrivere il
risultato e quindi non pu subito operare su V6[0], ma bisogna attendere un certo tempo, tempo morto.
La gestione dellhardware avviene con una serie di tabelle, allinterno del processore, stazioni di
prenotazione. Lunit di controllo prima di eseguire unistruzione, ad esempio la moltiplicazione, verifica in
questa tabella se i moltiplicatori sono disponibili. Se questi non sono liberi nella tabella scritto il
moltiplicatore impegnato dallistruzione x che opera con determinati registri. Allora mentre il
moltiplicatore impegnato per quellistruzione, non possibile imporre al moltiplicatore che per altri 2
colpi di clock di scrivere il risultato in un registro e per i restanti di scrivere in un altro registro. Bisogna
aspettare che la prima istruzione venga scollegata, al termine allora lunit funzionale potr fare unaltra
operazione. La dead time dura quanto la latenza dellunit funzionale. Se lunit funzionale a 18 stadi, si
attenderanno 18 colpi di clock. Quindi questo numero deve essere un buon numero. Pi sono gli stadi
maggiore la frequenza con cui posso inserire gli elementi nellunit funzionale, per maggiore la latenza
e la durata della dead time.
Il dead time critico con vettori corti. Perch si ha un operazione che efficiente in termini di durata e poi
c la dead time notevole. Se il vettore ha una coppia sola di operandi loggetto si tiene bloccato come se
non pipeline e dopo si pu inserire latra coppia.
La macchina di Cray invece gestisce bene la situazione eliminando la dead time consentendo lo
smistamento del risultato anche durante linput della prossima coppia di dati dellistruzione successiva. Cos
anche vettori con pochi elementi sono gestiti in maniera efficiente.

172

8. Alcune tipiche operazioni con i vettori


Consideriamo il seguente codice:
1. for(i=0; i<N; i++)
2.
A[B[i]]++;
vengono incrementati gli elementi di A indicizzati dal vettore B, si parla in questo caso di matrici sparse,
matrici i cui solo alcuni elementi sono realmente significativi, e si tiene traccia di questi elementi in un
vettore, che magari contiene il loro indirizzo. Questo vettore si comporta come indice.
Questo codice si traduce in:
1.
2.
3.
4.

LV vB, rB
LVI vA, rA, vB
ADDV vA, vA, 1
SVI vA, rA, vB

Si carica in vB il vettore che parte da rB. Allistruzione due si effettua una load indicizzata, in cui si carica in
vA gli elementi di A, ma solo gli elementi indicizzati dal vettore vB caricato prima. Allistruzione tre c la
somma vettoriale con scalare, avviene lincremento, e infine la store indicizzata dove si memorizzano solo
gli elementi indicizzati da vB.
Consideriamo un altro codice dove presente una codinzione:
1. for (i=0; i<N; i++)
2.
if (A[i]>0) then
3.
A[i] = B[i];
Il codice si traduce nel seguente:
1.
2.
3.
4.
5.

CVM
LV vA, rA
SGTVS.D vA, F0
LV vA, rB
SV vA, rA

Alcuni elementi di A, quelli negativi, saranno modificati da B. Si crea un operazione mascherata; la


maschera sar uguale a 1 per gli elementi in cui A maggiore di zero e poi condizionato a quella maschera
verr fatta la copia dellelemento i-esimo di B nellelemento i-esimo di A. Lelemento di B va in A solo se il
vettore maschera uguale a 1.
Dopo aver caricato in vA il vettore A, si setta il vettore maschera attraverso loperazione, SGTVS.D vA, F0
(set greather than vector scalar). Se lelemento i-esimo del vettore pi grande dello scalare F0 (F0
contiene 0 nei registri floating point), setta li-esimo elemento del vettore maschera altrimenti viene
resettato. La load successiva avviene mascherata. Il vettore maschera contiene dei flag, quindi dei bit per
indicare si o no. Quando c lo zero nella maschera listruzione sostituita con una NOP. Quindi listruzione
quattro, LV vA,rB, che fa la copia mascherata del vettore vB, indirizzato da rB, in vA, quando la maschera
conterr 1 copier lelemento di B in A, quando trova zero, NOP in sostanza, quindi non far la copia.
Loperazione mascherata non termina prima, dura quanto listruzione normale: sempre 64 colpi di clock.
173

La load dellindirizzo rB in vA legger la memoria e scriver in A solo se il flag uno altrimenti esegue la
NOP e quindi rimane il vecchio valore. Con la store termina. Il bit di maschera abilita eventualmente la
scrittura del dato. Loperazione viene comunque eseguita.
9. Riduzione a scalare dei vettori
Abbiamo visto che se le operazioni sono indipendenti possono essere eseguiti in pipeline. Se ci sono invece
relazioni vettoriali, come ad esempio nel prodotto scalare, in cui dopo aver fatto il prodotto degli elementi
omologhi dei due vettori, questi risultati vanno sommati tra loro, la somma di tutti gli elementi di un
vettore non lo stesso che fare la somma di due elementi di due vettori, questa somma crea una
dipendenza tra i dati.
Si effettua in questo caso la riduzione a scalare. Questa tecnica si utilizza il pi delle volte nel fare la somma
degli elementi di un registro vettoriale.
Consideriamo il prodotto scalare tra due vettori, V1 e V2, di N elementi. Si crea un nuovo vettore
V3 = V1*V2. Ora gli elementi di V3 devono essere sommati tra di loro in un accumulatore.
Si pu sfruttare la potenza del processore vettoriale pensando di dividere il vettore V3 a met in due
vettori, V3 e V3 e fare la somma tra questi due vettori come se fossero indipendenti, V4 = V3+V3.
Impostando quindi VLR pari a N/2. Si produce quindi un vettore con N/2 elementi ottenuto di fatto da N/2
somme scalari. Il vettore ottenuto, V4, viene ancora diviso a met in due vettori, V4 e V4 e fare una nuova
somma vettoriale, con elementi che questa volta sono di N/4, e via dicendo. La riduzione a scalare viene
fatta suddividendo di volta in volta a met i risultati di un primo step fino ad arrivare alla fine ad avere un
elemento di due elementi, che spezzettato in due vettori di un elemento ciascuno, sommati forniscono il
risultato finale.
Questo spezzettamento del vettore utile portarlo avanti fino al punto di breakeven point.

174

10. Set istruzioni del processore VMIPS


Set di istruzioni del processore MIPS vettoriale.

Nelle istruzioni si nota la presenza della lettera S in alcune a indicare che uno degli operandi dellistruzione
uno scalare, registro scalare, che lo stesso per tutti gli elementi del vettore. Loperazione SUB non
essendo commutativa c listruzione vettore meno scalare, e scalare meno vettore, questo vale anche per
la DIV. Tutte queste istruzioni hanno come registro destinazione un registro vettoriale.
Le operazioni di accesso a memoria, necessita lindirizzo del primo elemento del vettore, poi i successivi
indirizzi vengono calcolati da questo attraverso un unit dedicata. Loperazione dura 64 colpi di clock,
quanto la dimensione del vettore, a meno di un aumento delle corsie. Ad esempio con 4 corsie sono
necessari 16 colpi di clock. Tra queste operazione c anche quello con lo stride, LVWS (WS, with stride) o
SVWS. Queste operazioni indicano che gli elementi da caricare non sono contigui, uno di seguito agli altri,
ma bisogna caricare gli elementi che sono separati uno dallaltro di 100byte. Questo avviene quando si ha
una matrice in memoria caricata per righe, e se serve caricare una colonna di questa matrice, gli elementi
175

della colonna distano tra loro un certo numero di byte. C uno spostamento regolare degli elementi in
memoria. Infine ci sono le operazioni di accesso a memoria indicizzate, vanno a indicizzare in un altro
vettore gli offset dove andare a memorizzare il vettore. Questo nasce dallesigenza di caricare alcuni
elementi di un vettore, gli indirizzi di questi elementi del vettore sono memorizzati in un vettore, vettore
indice. Se gli spostamenti sono irregolari gli indirizzi degli elementi sono in un vettore indice. Questo
approccio utilizzato con le matrice sparse, matrici con molti zeri, e per trattare i numeri diversi da zero si
tiene tracci della loro posizione. Listruzione CVI crea il vettore indice.
Le istruzioni di compare fanno il confronto fra due vettori, o fra il vettore e uno scalare.
Listruzione POP conta gli elementi del vettore pari a 1 nel registro maschera e il risultato va in R1.
Listruzione CVM setta a uno il registro maschera.
E infine ci sono le istruzioni per lo spostamento da registri speciali a registri normali e viceversa.
Un registro speciale il registro VLR che indica al sistema quanti elementi del registro vettoriale ha
effettivamente senso utilizzare. Il registro vettoriale ha 64 elementi, per se il problema specifico da
affrontare lavora con vettori di 20 elementi, di questi 64 elementi ne servono solo 20. Quindi durante le
operazioni settando VLR a 20, si lavora su un numero di elementi pari a 20, VLR. In VLR si scrive il valore che
la dimensione reale del vettore, chiaro che non possibile scrivere un numero maggiore di 64.

176

Esercizi
Esercizio n.1
Dati due vettori A e B di N elementi double, con N<=64 e memorizzato nel registro R6. Lindirizzo del primo
elemento di A memorizzato nel registro R7; mentre B il vettore riga di una matrice NxM, caricata in
memoria per righe, dove il valore di M nel registro R5. Lindirizzo del primo elemento di B, corrispondente
allelemento della matrice, nel registro R8. Considerato inoltre lo scalare q nel registro F8, calcolare e
memorizzare, attraverso un processore vettoriale, a partire dallindirizzo nel registro R9 il vettore C=Aq +B.
a. Supponendo che ci siano 1 lane, e che la latenza del moltiplicatore sia pari a 6 c.c. e la
latenza del sommatori pari a 2 c.c. Effettuare lanalisi temporale delle istruzioni scritte
prima.
b. Ripetere lanalisi temporale considerando 4 lanes.
c. Scrivere il codice per un processore scalare, effettuare lanalisi temporale e confrontare con
quelle fatte ai punti a e b.
Soluzione:
1. MTC1 VLR, R6
//VLR <- N
2. DSLL R5,R5,3
//R5<-M*8
3. LV V1, R7
//carico gli N elementi di A in V1
4. LVWS V2,(R7,R5) //carico gli B con stride(in byte)
5. MULVS.D V3,V1,F8 //V3 = A*q
6. ADDV.D V4,V3,V2 //V4 = A*q + B
7. SV V4,R9
a. Analisi temporale con 1 lane, considerando la latenza del moltiplicatore pari a 6 c.c. e quella del
sommatore pari a 2 c.c. Inoltre consideriamo una banda tripla per lane, in modo tale da poter
accedere alla memoria con tre istruzioni di tipo load o store contemporaneamente. Effettuiamo
lanalisi temporale considerando vettori di 64 elementi, N = 64.
MTC1
DSLL
LV
LVWS
MULVS.D
ADDV.D
SV

IF

ID
IF

EXE
ID
IF

MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

69

70

71

78

30

WB
EXE
ID
IF

ID
IF

EXE (6 c.c.)
stallo
stallo
EXE
stallo

stallo
ID

MEM (64 c.c.)


WB
MEM (64 c.c.)
WB
EXE succ. elementi estratti dalla memoria (63 c.c.)
MEM
WB
stallo
stallo
EXE (2 c.c.)
EXE succ. elementi prodotti dal moltiplicatore (63 c.c.)
stallo
stallo
stallo
stallo
MEM (64 c.c.)

MEM

WB

Sono necessari 78 colpi di clock.


La fase di EXE della MULVS.D e della ADDV.D sono pi lunghe perch processano tutte gli altri
dati che vengono prelevati dalla memoria, oppure prodotti dal moltiplicatore nel caso della
ADDV.S . Essendo pipelineizzate queste producono il primo risultato dopo 6 c.c. per quanto
riguarda il moltiplicatore e invece dopo 2 c.c. per il sommatore, per i risultati successivi sono
prodotti uno ad ogni colpo di clock.
Considerando 4 lanes invece,
MTC1
DSLL
LV
LVWS
MULVS.D
ADDV.D
SV

IF

ID
IF

EXE
ID
IF

MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

21

21

23

WB
EXE
ID
IF

MEM (16 c.c.)


MEM (16 c.c.)
ID
IF

stallo
ID

EXE (6 c.c.)
stallo
stallo
EXE
stallo

stallo
stallo

stallo
stallo

WB
WB
EXE succ. elementi estratti dalla memoria ( 15 c.c.)
MEM
WB
EXE (2 c.c.)
EXE succ. elementi prodotti dal moltiplic. (15 c.c.)
stallo
stallo
MEM (16 c.c.)

Sono necessari 30 colpi di clock, che non sono come vediamo un quarto dei c.c. con 1 lane.
b. Considerando un processore scalare, il codice ottimizzato potrebbe essere:

177

MEM

WB

1.
2.
3. loop:
4.
5.
6.
7.
8.
9.
10.
11.
12.

DADDI R1, (R6)0


DSLL R5,R5,8
L.D F1,R7
L.D F2,R8
MUL.D F3,F1,F8
DADDI R7,(R7)8
DADD R8,R8,R5
ADD.D F4,F3,F2
S.V F4,R9
DADDI R1,(R1)-1
BNEZ R1, loop
DADD R9,R9,8

//R1<- N
//R5 <- M*8
//A[i]
//B[i]
//A[i]*q

//C[i] = A[i]*q+B[i]

Il diagramma temporale

DADDI
DSLL
L.D
L.D
MUL.D
DADDI
DADD
ADD.D
S.V
DADDI
BNEZ
DADD

IF

ID
IF

EXE
ID
IF

MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

WB
MEM

WB

ID
IF

EXE
ID
IF

10

EXE (6 c.c.)
MEM
WB
EXE
MEM
ID
stallo
IF
ID
IF

11

WB
stallo
EXE
ID
IF

12

stallo
stallo
EXE
ID
IF

13

14

MEM

WB

EXE (2 c.c.)
stallo
stallo
MEM
WB
EXE
MEM
ID
EXE

15

16

MEM
MEM

WB

WB
MEM

WB

Sono necessari 14*N+2 c.c. Considerando N=64, sono necessari 898 c.c.
Possiamo inoltre svolgere questo con la pipeline da programma:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.

DADD R10,R0,R6
DADDI R4,(R0)2
DSLL R5,R5,3
L.D F1,(R7)0
L.D F2,(R8)0
L.D F3,(R7)8
DADD R8,R8,R5
L.D F4,(R8)0
MUL.D F5,F1,F8
ADD.D F6,F1,F2
DADDI R7,(R7)16
DADD
---------------------------------------loop:
S.D F6,(R9)0
MUL.D F5,F3,F8
ADD.D F6,F5,F4
L.D F3,(R7)0
L.D F4,(R8)0
DADDI R7,(R7)8
DADD R8,R8,R5
DADDI R9,(R9)8
178

R8,R8,R5

21.
22.

ADDI R10,(R10)-1
BNEQ
R10,R4,
---------------------------------------S.D F6,(R9)0
MUL.D F5,F3,F8
ADD.D F6,F5,F4
S.D F6,(R9)8

23.
24.
25.
26.

loop

Esercizio n. 2
Ripetere lesercizio 1, per un processore vettoriale, con il vettore B caricato in maniera adiacente, e
considerando N>64.
Soluzione:
1.
2. loop:
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

ANDI R1,R6,63 //R1<-N mod 64


MTC1 VLR,R1
//VLR <- R1
L.V V1,R7
//V1<- A[R5/8&A[0]]
L.V V2,R8
//V1<- B[R5/8&A[0]]
MULVS.D V3,V1,F8 //V3<-A*q
DSLL R5,(R1)8 //R5= dim. byte del pezzo di vettore
DADD R7,R7,R5 //aggiorno R7 al nuovo elemento di A
DADD R8,R8,R5 //aggiorno R8 al nuovo elemento di B
DSUB R6,R6,R1 //N<- N (dim. Vettore processato)
DADDI R1,(R0)64 //R1 <-64
ADDV.D V4,V3,V2 //V4<- Aq +B
S.V V4,R9
BNEZ R6,loop
DADD R9,R9,R5 //aggiorno R9 al nuovo elemento di C

Ci sono tante iterazioni per quanti pezzi stato diviso il processore pi literazione dovuto alla
computazione del resto, N/64 +1.
a. Fissato N=1000, indicare quanto dura la computazione, considerando che ci sono 4 lanes e
che la latenza del moltiplicatore di 6 c.c., mentre quella del sommatore 2 c.c.
b. Ripetere la computazione con 1 lane.
Soluzione
a. N = 1000, con 4 lane
ANDI
MTC1
L.V
L.V
MULVS.D
DSLL
DADD
DADD
DSUB
DADDI
ADDV.V
S.V
BNEZ
DADD

IF

ID
IF

EXE
ID
IF

MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

WB
EXE
ID
IF

ID
IF

EXE
ID
IF

MEM ( 10 c.c. / 16 c.c.)


MEM (10 c.c. / 16 c.c.)
EXE (6 c.c.)
MEM
WB
EXE
MEM
WB
ID
EXE
MEM
WB
IF
ID
EXE
MEM
IF
ID
EXE
IF
ID
IF

179

WB
WB
EXE elementi successivi prelevati dalla memoria (9 c.c. / 15 c.c.) MEM

WB
MEM
WB
EXE (2 c.c.)
ID
stallo
IF
ID
IF

WB

EXE elementi successivi prodotti dalla moltiplicazione (9 c.c. / 15c.c.)


MEM
EXE
MEM (10 c.c. / 16 c.c.)
stallo
EXE
MEM WB
ID
stallo
EXE
MEM WB

WB

b. N = 1000, considerando 1 lane. Le fasi di MEM durano

c.c, quindi 40 c.c. per la prima

iterazione e 64 c.c. per le successive.


ANDI
MTC1
L.V
L.V
MULVS.D
DSLL
DADD
DADD
DSUB
DADDI
ADDV.V
S.V
BNEZ
DADD

IF

ID
IF

EXE
ID
IF

MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

WB
EXE
ID
IF

ID
IF

EXE
ID
IF

MEM ( 40 c.c. / 64 c.c.)


MEM (40 c.c. / 64 c.c.)
EXE (6 c.c.)
MEM
WB
EXE
MEM
WB
ID
EXE
MEM
WB
IF
ID
EXE
MEM
IF
ID
EXE
IF
ID
IF

WB
WB
EXE elementi successivi prelevati dalla memoria (39 c.c. / 63 c.c.) MEM

WB
MEM
WB
EXE (2 c.c.)
ID
stallo
IF
ID
IF

WB

EXE elementi successivi prodotti dalla moltiplicazione (39 c.c. / 63c.c.)


EXE
MEM (40 c.c. / 64 c.c.)
stallo
EXE
MEM WB
ID
stallo
EXE
MEM WB

MEM

WB

Sono necessari N/64 = 16 +1 iterazioni. Per un totale di 18 c.c. ad iterazione, sono necessari 17*18+1 =
307c.c.
Esercizio n.3
Considerando i dati dellesercizio 1, dato il vettore A di N elementi caricato in memoria a partire
dallindirizzo memorizzato in R7. Il valore di N nellindirizzo R6. Considerato lo scalare q in F8, determinare
il vettore C=A*q, sapendo che il vettore C caricato in memoria a partire dallindirizzo memorizzato in R9.
Si effettui il calcolo su un processore scalare utilizzando la tecnica del VLIW, formata da:
-

2 operazioni di accesso a memoria;


2 operazioni per il calcolo floating point
1 operazione di ALU o di branch

Inoltre larchitettura prevede:


-

2 moltiplicatori
Larghezza di banda tale da prelevare due dati simultaneamente

Soluzione:
Un possibile svolgimento dellalgoritmo potrebbe essere il seguente:
1.
DADD R1,R6,R0
//R1<- N, contatore
2. loop: L.D F1,(R7)0
//F1<- &A[i], i=N-R1
3.
MUL.D F1,F1,F8 //F1<- A[i]*q
4.
S.D F1,(R9)0
//M[R9]<-F1
5.
DADDI R7,(R7)8 //incremento R7 alla nuova pos
6.
DADDI R9,(R9)8 // incremento R8 alla nuova pos
7.
DADDI R1,(R1)-1 //decremento R1
8.
BNEZ R1,loop (-7)
Un ottimizzazione del codice potrebbe essere:
1.
DADD R1,R6,R0
2. loop: L.D F1,(R7)0
3.
MUL.D F1,F1,F8
4.
DADDI R7,(R7)8
5.
DADDI R1,(R1)-1
6.
S.D F1,(R9)0

//R1<- N, contatore
//F1<- &A[i], i=N-R1
//F1<- A[i]*q
//incremento R7 alla nuova pos
//decremento R1

180

7.
8.

BNEZ R1,loop (-6)


DADDI R9,(R9)8 // incremento R8 alla nuova pos

Per una durata pari a 1+11*N c.c.


Per eseguire il VLIW procediamo con lo srotolamento del loop per 8 iterazioni, evitando in questo
modo il pi possibile gli stalli:
1.
2. loop:
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.

DADD R1,R6,R0
//R1<- N, contatore
L.D F1,(R7)0
//F1<- &A[i], i=N-R1
L.D F2,(R7)8
//F1<- &A[i+1]
L.D F3,(R7)16
//F1<- &A[i+2]
L.D F4,(R7)24
//F1<- &A[i+3]
L.D F5,(R7)32
//F1<- &A[i+4]
L.D F6,(R7)40
//F1<- &A[i+5]
L.D F7,(R7)48
//F1<- &A[i+6]
L.D F9,(R7)56
//F1<- &A[i+7]
MUL.D F1,F1,F8 //F1<- A[i]*q
MUL.D F2,F2,F8 //F1<- A[i+1]*q
MUL.D F3,F3,F8 //F1<- A[i+2]*q
MUL.D F4,F4,F8 //F1<- A[i+3]*q
MUL.D F5,F5,F8 //F1<- A[i+4]*q
MUL.D F6,F6,F8 //F1<- A[i+5]*q
MUL.D F7,F7,F8 //F1<- A[i+6]*q
MUL.D F9,F9,F8 //F1<- A[i+7]*q
S.D F1,(R9)0
S.D F2,(R9)8
S.D F3,(R9)16
S.D F4,(R9)24
S.D F5,(R9)32
S.D F6,(R9)40
S.D F7,(R9)48
S.D F9,(R9)56
DADDI R7,(R7)72
DADDI R1,(R1)-9 //decremento R1 per il

numero

di

volte

//in cui ho srotolato il loop

28.
29.
C.C.
1
2
3
4
5
6
7
8
9
10
11
12

BNEZ R1,loop
DADDI R9,(R9)72

MEM1

MEM2

L.D F1,(R7)0
L.D F3,(R7)16
L.D F5, (R7)32
L.D F7,(R7)48

L.D F2,(R7)8
L.D F4,(R7)24
L.D F6,(R7)40
L.D F9,(R7)56

FP1

FP2

ALU(o branch)
DADD R1,R6,R0

S.D F1,(R9)0
S.D F3,(R9)16
S.D F5,(R9)32
S.D F7,(R9)48

MUL.D F1,F1,F8
MUL.D F3,F3,F8
MUL.D F5,F5,F8
MUL.D F7,F7,F8

S.D F2,(R9)8
S.D F4,(R9)24
S.D F6,(R9)40
S.D F9,(R9)56

MUL.D F2,F2,F8
MUL.D F4,F4,F8
MUL.D F6,F6,F8
MUL.D F9,F9,F8

DADDI R7,(R7)64
DADDI R1,(R1)-8

BNEZ R1, loop


DADDI R9,(R9)64

181

Per un totale di 1+11*(N-8) c.c. In 12 colpi di clock vengono processate 29 istruzioni, con una frequenza pari
a 2.41 istruzioni per colpo di clock.

Esecizio n.4
Considerando i dati dellesercizio 1, dati due vettori A e B, di N elementi, scrivere il codice per eseguire
C=A/B, evitando divisioni per 0; poi salvare C in memoria. Il processore ha una lane e la latenza del divisore
di 20 c.c.

Svolgimento:
1.
2.
3.
4.
5.
6.
7.

MTC1 VLR,R6 //VLR<- N


DSLL R5,R5,3 //R5 lo stride in byte per caricare gli elementi di B
LV V1,R7
LVWS V2,R8,R5 //stride:byte di cui spostarsi
SNEV.D V2,F0 //setto la maschera sugli elementi diversi da 0
DIVV.D V3,V1,V2 //effettuo la divisione mascherata: elementi di V2 diversi da
S.V V3,R9

Un analisi temporale del programma la seguente:


MTC1
DSLL
LV
LVWS
SNEV.D
DIVV.D
S.V

IF

ID
IF

EXE
ID
IF

MEM
EXE
ID
IF

WB
MEM
EXE
ID
IF

69

70

70

WB
EXE
ID
IF

EXE
ID
IF

MEM
ID

MEM(64 c.c.)
WB
MEM(64 c.c.)
WB
WB
EXE (20 c.c.)
EXE elementi successivi prelevati dalla memoria (63 c.c.)
stallo (19 c.c.)
EXE
MEM(64 c.c.)

MEM

WB

Per un totale di 92 c.c.


Esecizio n. 6
Dato il vettore A, con N=64 elementi, e la matrice H di NxM elementi, con M =64. Determina la matrice
C=A*q + B, dove B un vettore colonna di H. Le colonne di C sono calcolate attraverso le colonne di H: Cji
=A*q+Bi. Per determinare la colonna i-esima di C, B punta alla colonna i-esima di H. Il valore di M in R5, il
valore di N in R6. Lindirizzo del primo elemento di A in R7, lindirizzo del primo elemento della matrice
H in R8. Lindirizzo del primo elemento di C in R9. La matrice H e la matrice C sono caricate in memoria
per righe.
Svolgimento:
1.
2.
3.
4.
5. loop:
6.

LV V1,R7
//V1<- A[063]
DADDI R2,(R0)64 //R2 contatore loop
MULVS.D V1,V1,F8 //V1<-A*q
SV V1,R7
//sovrascrivo in mem al posto di A, A*q
LV V2,R8
//V2<- B: elementi della i-esima riga di H
L.D F1,R7 //R1<- A[i]*q, lo utilizzo come scalare
182

7.
8.
9.
10.
11.
12.
13.
LV
DADD
MULVS.D
SV
LV
L.D
DADDI
ADDVS.D
DADDI
DADDI
SV
BNEZ
DADDI

DADDI R7,(R7)8 //i++


ADDVS.D V2,V2,F1 //V2(j)<-V2(j)+ (A[i]*q)
DADDI R8,(R8)512 //riga successiva
DADDI R2,(R2)-1
SV V2,R9
BNEZ R2, loop
DADDI R9,(R9)512 //riga successiva
IF

ID
IF

EXE
ID
IF

MEM (64 c.c.)


EXE
ID
IF

MEM

WB

ID
IF

stallo
ID
IF

EXE (6 c.c.)
stallo
stallo
EXE
ID
EXE
IF
ID
IF

WB
elementi succ. prelevati dalla memoria (63 c.c.)
MEM
EXE
MEM (64 c.c.)
MEM (64 c.c.)

stallo

stallo

MEM
EXE
ID
IF

WB
MEM
WB
EXE (2 c.c.)
ID
EXE
IF
ID
IF

Per un totale di 4+64*(13) c.c.

183

MEM
EXE
ID
IF

WB
WB

EXE succ. elementi (63 c.c.)


WB
MEM
WB
EXE
MEM (64 c.c.)
ID
EXE
MEM
WB
IF
ID
EXE
MEM
WB

MEM

WB

184

CAPITOLO 21

TASSONOMIA DI FLYNN
1. Classificazione dei processore secondo Flynn
Flynn classifica i sistemi di calcolo a seconda della molteplicit del flusso di istruzioni e del flusso dei dati
che possono gestire. Combina questi flussi secondo laspetto se sono singoli o multipli a creare una serie di
possibili macchine di calcolo.
-

SISD (Single Istruction Single Data), non un singola istruzione, ma singolo flusso di istruzioni, e
singolo insieme di dati. Un insieme di dati sui quali si applica un unico processore. Uniprocessore
(processore monotask), che dati dei dati e un programma esegue solo quel programma su quei dati.
SIMD (Single Istruction Multiple Data) su un set multiplo di dati si esegue lo stesso flusso di
istruzioni. La stessa applicazione su un set multiplo di dati, e questo pu essere realizzabile con
processori in parallelo. Ad esempio data una serie di immagini sulle quali bisogna effettuare un
equalizzazione. Il flusso di istruzioni lo stesso, lequalizzazione, e su tutte queste immagini
loperazione da fare la stessa. Si pu pensare a pi processori in parallelo sui quali caricare lo
stesso codice e ciascun processore andr a leggere il suo set di dati.
MIMD (Multiple Istruction Multiple Data) pi flussi di istruzioni su pi set di dati. Su un processore
viene fatta una certa operazione su determinati dati, su un altro processore invece vengono
eseguite operazioni completamente diverse su dati ancora diversi. Questo sistema non richiede
interconnessioni dato che ogni processore ha il suo flusso di dati e il suo codice da eseguire. Trova
interesse nei clusters, un insieme di computer connessi tramite una rete telematica. Nelle
workstation in rete a cui un utente si collega e comincia ad effettuare delle operazione con i suoi
dati e intanto altri utenti eseguono altre operazioni con i loro dati differenti.
MISD (Multiple Istruction Single Data) sugli stessi dati bisogna eseguire diversi algoritmi. Potrebbe
avere senso pensare a un sistema che con un unico set di dati suddiviso fra i vari processi da fare su
unit di calcolo differenti. Questo approccio non ha diffusione poich non semplice avere casi
reali di questo genere e non interessante dal punto di vista architetturale. (Questa architettura
a livello teorico privo di utilizzo pratico).

Dal punto di vista architetturale le macchine SIMD sono le pi interessanti, elaborano un task su pi
dati in parallelo. Le schede video sono lesempio pi recente di macchine SIMD.
La programmazione multithread simula un sistema MIMD.
2. Concetti base
-

Parallel Computer: insieme di diversi elementi distinti, diverse CPU, che cooperano per risolvere lo
stesso problema in maniera pi veloce oppure risolvono un macroproblema comunicando tra di
loro. Un architettura parallela prevede una definizione dellarchitettura di comunicazione fra i vari
nodi di questo sistema.
Multiprocessori a memoria centralizzata: la memoria
centralizzata
risponde
alla
definizione
della
comunicazione. Non ci si preoccupa della comunicazione
185

fra i vari processore, la memoria essendo visibile a tutti i processori, lo scambio di informazioni
avviene sulla memoria centrale. Questo comporta che la memoria centrale abbia una banda tale da
non penalizzare un accesso simultaneo alla memoria di pi processore. Nella realt i sistemi a
memoria centralizzata non hanno un grande numero di core. Esistono strutture ibride in cui gruppi
di processori lavorano con la loro memoria centralizzata che ha una infrastruttura di comunicazione

con altri cluster.


Memoria distribuita: un sistema che vede ad ogni processore un
assegnazione di memoria che essendo privata non ha problemi di
banda, poich a quella memoria accede solo quel processore e bisogna
far si che la comunicazione avvenga a livello di connessione tra questi
moduli. Ciascun processore ha la sua memoria, e tutti questi moduli
hanno un infrastruttura che li connetta.

Su un approccio SIMD preferibile utilizzare un approccio con memoria distribuita. Il software va re


ingegnerizzato dovuto al fatto che c maggiore banda di memoria che deve essere sfruttata.

186

CAPITOLO 22

INFRASTRUTTURE DI COMUNICAZIONE
1. Bus
Il modo pi semplice per mettere in comunicazione due nodi dal punto di vista dellespandibilit e del costo
il bus (omnibus, per tutti). Il bus un canale su cui tutti hanno diritto a stare. Il problema che su
questo canale c un traffico massimo dato dalla banda del canale. Se c un dispositivo che sta scrivendo
sul bus, nessunaltro pu scrivere. Per possibile leggere in simultanea. Se due dispositivo voglio entrambi
scrivere sul bus, c un dispositivo che funge da master del bus che da il permesso ai dispositivi di scrivere.
Esistono delle priorit di accesso alla risorsa, e chi pi vicino al master si vedr offrire per primo la
possibilit di scrivere per primo, mentre chi pi lontano potr scrivere solo se quelli prima di lui non
stanno scrivendo.

C una espandibilit teoricamente infinita. Si possono attaccare sul bus infiniti dispositivi. Un dispositivo
per essere collegato al bus necessario che abbia una porta bidirezionale in lettura e scrittura. Per
possibile che molti dispositivi non potranno mai scrivere sul bus per il motivo delle priorit, quindi come
se non ci fossero. Il problema legato quindi allefficienza, poich si occupa una banda vincolando tutti gli
altri dispositivi.
2. Rete punto-punto
Dato un certo numero di sistemi e questi sono tutti collegati tra
di loro. Questa connessione dal punto di vista delle prestazioni
molto efficiente, ci sono un certo numero di connessioni ognuno
con la sua banda. Se due nodi A e B dialogano con una banda
elevata non crea alcun disturbo magari a C e D che stanno anche
dialogando tra loro. Questa sembra una comunicazione ideale. Il
problema che questa comunicazione non espandibile. Poich
quando si inserisce un nodo comporta che questo deve essere collegato con tutti gli altri. Ogni sistema ha
un certo numero di porte che permette di connettersi agli altri sistemi, per cui si deve avere un numero di
porte capace di sostenere tutte le connessioni con tutti i nodi. Questa interfaccia la deve avere ogni
sistema. Quindi quando si progetta questo sistema bisogna stabilire il numero massimo di elementi che
possono essere connessi. Lespandibilit destramente ridotta. Deciso il numero di porte, si possono avere
tante connessioni quante sono le porte.
In un infrastruttura con N nodi, ogni nodo ha N-1 porte. Quante connessione ci sono? Il primo nodo viene
collegati agli N-1 nodi. Il secondo nodo dovr essere collegati agli N-1 nodi, per la connessione verso il
187

primo c gi, quindi N-2. Il terzo dovr collegarsi verso tutti tranne che con il primo e il secondo, N-3. Il
penultimo dovr collegarsi con lultimo, mentre lultimo non dovr collegarsi pi con nessuno visto che
sono gi tutti collegati. Ora le connessioni totali sono la somma di tutti questi

Quindi la rete ha

connessioni.

Con questo tipo di rete quando si invia un dato, questo arriva subito al destinatario, in un passaggio, in un
solo colpo.
3. Rete ad anello
Lanello(ring) prevede che ogni nodo sia connesso solo a una coppia di
nodi. Lanello rispetto al bus prevede una situazione diversa dal bus in
cui mentre un dispositivo parla, gli altri non possono farlo.
Nellanello le linee di connessione sono indipendenti, quindi nascono
vantaggi dal punto di vista della comunicazione concorrente, nello
stesso istante ci sono pi comunicazioni.
C un vantaggio dovuto al fatto che il sistema pi espandibile rispetto alla rete punto-punto. Ogni nodo
ha sempre due porte.
Il problema di natura pratica, perch se lanello molto grande, succede che due nodi che vogliono
parlare, la comunicazione prevede un certo numero di tratta da attraversare.
Distinguiamo:
-

Ring monodirezionale: la comunicazione avviene solo in un senso.


Ring bidirezionale: la comunicazione avviene nei due versi, orario e antiorario.

Sul ring sia invia un dato con un indirizzo, numero del nodo destinatario. Questo viaggia sul canale, arriva a
un certo nodo, legge dal numero che il dato non riguarda lui e lo invia in uscita. Questo si ripete fino a
quando il dato non arriva al nodo destinatario corretto. Quindi c un certo numero di passaggi per arrivare
al destinatario. Che dipende da dove si trova il nodo destinatario. Per un ring monodirezionale, in media
supponendo le comunicazioni equiprobabili ci sono N/2. Se invece la frequenza di invio dati ha una
preferenzialit su alcuni nodi, e questi sono anche vicini, allora la probabilit che duri 1 alta e il valore
medio pi basso N/2. Con un ring bidirezionale, nellipotesi pi peggiore, il destinatario si trova a N/2,
quindi in media ci sono N/4 passaggi.
Per cui c un costo pi basso, poich con N nodi, sono necessari N linee di connessione, contro quelle della
rete punto-punto. Per la comunicazione avviene in N/2 passaggi o N/4 se bidirezionale.

188

4. Rete a centro stella


Meccanismo di routing, in cui ciascun nodo collegato un nodo centrale,
centro stella. Ogni elemento ha una porta di ingresso-uscita collegato al
centro stella. In questo modo c una massima espandibilit. Il centro
stella deve inviare un pacchetto che arriva da un nodo al nodo
destinatario.

5. Reti ibride
Il ring poco costoso e abbastanza efficiente con pochi elementi, si pensa a un set di ring che vengono
collegati con un meccanismo di centro stella. Con il vantaggio che gli elementi dello stesso ring non
utilizzano il centro stella. Ci sono una serie di reti locali dove avviane gran parte del traffico, e poi queste
sono collegati ad altre reti locali. Quello che avviene nella rete telefonica, con il prefisso: c uno stesso
distretto e le telefonate bari su bari costavano messo delle chiamate bari roma, poich dovevano utilizzare
altre risorse per collegarsi con Roma.
6. Ipercubo
Nel ring il costo contenuto, la banda interessante poich sulle n linee viaggiano in parallelo i dati, per si
paga la latenza. Sul ring in ogni tratta c un pacchetto che viaggia. I canali sono tutti pieni, tutti i dati sono
in viaggio. La banda molto elevata. Nel bus la banda piena ma legata solo a una comunicazione. Un
sistema che cerca di creare un compromesso la connettivit a ipercubo. Lipercubo contiene il numero di
costi dal punto di vista di linee di connessione, mantenendo elevato il traffico sia dal punto di vista di banda
che dal numero di tratte da percorrere durante una comunicazione.
Si definisce dimensione dellipercubo, il numero di linee di ingresso/uscita, quindi linee bidirezionali, di ogni
nodo e in sostanza definisce la popolazione dei nodi dellipercubo perch questa dimensione legata al
numero dei nodi. Un ipercubo di dimensione 3 indica che ogni nodo ha 3 linee di ingresso e uscita.
Dimensione 0, indica che non c nessuna linea di ingresso/uscita, lipercubo ha un solo nodo che non
comunica con nessuno.
Dimensione 1, c una linea per ogni nodo, questo comporta che ci sono due soli nodi. Lipercubo di
dimensione 1 composta da due nodi.
Dimensione 2, ci sono due linee per ogni nodo. Questo comporterebbe 3 nodi, per si cerca di aumentare
la dimensione e sono sufficienti infatti 4 nodi.
Dimensione 3, con 3 linee per ogni nodo, comporta che ci siano 8 nodi.

189

Un ipercubo di dimensione k si ottiene connettendo due ipercubi di dimensione k-1, in particolare


collegando tutti i nodi di questi due ipercubi di dimensione inferiore, ognuno al suo omologo.
Data la dimensione D, ci sono N = 2D nodi.
N = 2D nodi
Alla dimensione come abbiamo detto prima collegata la popolazione dellipercubo, N = 2D. Ogni nodo ha
D linee, quindi ci sono
connessioni

Dim
Ring
Punto-punto
Ipercubo

Nodi
N
N
2D

Linee
N
N(N-1)/2
D2(D-1)

Latenza
N/2
1
D

Consideriamo un ipercubo di dimensione D=10.

Ring
Punto-punto
Ipercubo

Dim
/
/
10

Nodi
1024
1024
1024

Linee
1024
0.5 mln
5120

Latenza
500 (250)
1
10

Costi/benefici
512000
0.5 mln
51200

Per costo si definisce il numero delle linee, mentre per benefici linverso della latenza,
6.1. Trasferimento dei dati allinterno dellipercubo
Consideriamo un ipercubo di dimensione 4. In generale il trasferimento di dati avviene con un protocollo
sottoforma di pacchetto. Il pacchetto costituito da una sequenza di bit. La prima sequenza chiamata
header, contiene informazioni sul pacchetto, unaltra parte chiamata data, contiene linformazione
significativa, ed inoltre c eventualmente un'altra parte che indica la fine del pacchetto.

190

header

data

fine pacchetto

Lheader contiene sostanzialmente lindirizzo del nodo a cui trasferire il dato, nodo di destinazione. Diventa
antipatico quando i bit necessari al dato necessario sono minori rispetto ai bit necessari allheader. Cio per
trasferire un dato di 1 bit necessario utilizzare 20bit per lheader. In questo caso si parla di overhead di
pacchetto. Si preferisce avere pacchetti con un piccolo overhead altrimenti si paga un inefficienza della
rete.
Bisogna assegnare a ogni nodo dellipercubo un indirizzo, da indicare nellheader. Si decide di dare un
numero a ogni nodo. Essendo lipercubo di dimensione 4, ci sono 16 nodi. Per esprimere 16 nodi sono
necessari 4 bit. Ogni nodo non pu avere lo stesso nome di un altro nodo.

Tra due nodi collegati orizzontalmente varia solo il bit meno significativo:
0000 -> 0001
Tra due nodi collegati verticalmente varia solo il secondo bit meno significativo:
0001 -> 0011

Tra due nodi collegati in diagonale varia solo il secondo bit pi significativo:
0010 -> 0110

Tra due nodi che si trovano su cubi differenti varia solo il primo bit pi significativo

0101 -> 1101

191

Analizziamo il caso il caso di un pacchetto di dati che deve andare dal nodo 0000 al nodo 1111. Questo
pacchetto nellheader avr lindirizzo 15:
1111 dato

Chiamiamo porta A la porta orizzontale, porta B la porta verticale, porta C la porta diagonale e porta D la
porta con laltro cubo.
Definiamo il protocollo:
1) Il nodo di partenza, 0000, confronta il bit meno significativo dellheader, 1111 con il suo bit meno
significativo, 0000 attraverso XOR. Se XOR da 1(i bit sono differenti) il pacchetto viene spedito sulla
porta A, al nodo corrispondente in orizzontale, 0001.
2) Il nodo in orizzontale 0001 vede arrivare il pacchetto e confronta il secondo bit meno significativo
dellheader, 1111, con il suo secondo bit meno significativo, 0001, attraverso XOR. Se lXOR
produce 1 invia il pacchetto sulla porta B, cio al nodo corrispondente in verticale, 0011.
3) Il terzo nodo, 0011, confronta il terzo bit meno significativo dellheader,1111, con il suo terzo bit
meno significativo, 0011, attraverso lXOR. Se sono diversi invia il pacchetto sulla porta C, al nodo in
diagonale, 0111.
4) Il nodo 0111 ricevuto il pacchetto verifica se il primo bit dellheader, 1111, coincide con il suo primo
bit, 0111, attraverso lXOR. Se questi sono differenti invia il pacchetto sulla porta D al nodo di
destinazione 1111.
5) La trasmissione terminata. Il pacchetto giunto al nodo di destinazione
Sono stati impiegati 4 colpi di clock, 4 passaggi. Latenza pari a 4, dimensione dellipercubo.
Ora ipotizziamo che il nodo di destinazione il nodo 0101, lheader questa volta contiene 0101.
1) Il nodo 0000, attraverso lXOR verifica se il bit meno significativo dellheader, 0101, diverso dal
suo bit meno significativo 0000. LXOR produce 1 e il nodo invia sulla porta B il pacchetto, al nodo
orizzontale 0001.
2) Il nodo 0001 ora confronta il secondo bit meno significativo dellheader 0101 con il suo secondo bit
meno significativo 0001. Questa volta lXOR produce zero e lungo la linea B non invia nulla.
3) Sempre il nodo 0001 confronta il terzo bit meno significativo dellheader,0101 con il suo terzo bit
meno significativo, 0001. LXOR produce 1 e il bit viene inviato sulla linea C questa volta, al nodo
0101.
4) Il pacchetto giunto a destinazione, ma il nodo non lo sa. Il nodo 0101 confronta il primo bit
dellheader 0101 con il suo primo bit 0101. LXOR restituisce 0 e il nodo non verr inviato.
5) La trasmissione terminata. Il pacchetto giunto a destinazione.
Le fasi sono sempre pari a D.
In generale il protocollo questo:
le 4 fasi fungono cos: ogni nodo ha un buffer di ingresso e uscita dove arrivano i pacchetti. A un certo colpo
di clock il nodo che deve inviare mette il pacchetto nel buffer di uscita.

192

Primo colpo di clock tutti i nodi mettono in XOR il primo bit meno significativo con il loro primo bit
meno significativo. Se lXOR restituisce 1 inviano il pacchetto sul canale A(orizzontalmente),
altrimenti non inviano nulla.
Secondo colpo di clock: i nodi mettono in XOR il secondo bit meno significativo dellheader con il
loro secondo bit meno significativo. Se lXOR restituisce 1 inviano il pacchetto sul canale
B(verticalmente), altrimenti non inviano nulla.
Terzo colpo di clock: i nodi mettono in XOR il terzo bit meno significativo dellheader con il loro
terzo bit meno significativo. Se lXOR restituisce 1 inviano il pacchetto sul canale C(diagonalmente),
altrimenti non inviano nulla.
Quarto colpo di clock: i nodi mettono in XOR il primo bit dellheader con il loro primo bit. Se lXOR
restituisce 1 inviano il pacchetto sul canale D(altro cubo), altrimenti non inviano nulla, questo nodo
quello di destinazione. Il pacchetto giunto a destinazione.

ipercubo

ring

punto-punto

Dim

nodi

linee

latenza

nodi

linee

latenza

nodi

linee

latenza

2
4
6
8
10

4
16
64
256
1024

4
32
192
1024
5120

2
4
6
8
10

4
16
64
256
1024

4
16
64
256
1024

2
8
32
128
512

4
16
64
256
1024

6
120
2016
32640
523776

1
1
1
1
1

E possibile anche definire un rapporto tra costo e prestazione


Ipercubo
8
128
1152
8192
51200

ring
8
128
2048
32768
524288

punto-punto
6
120
2016
32640
523776

I nomi ai nodi dellipercubo sono assegnati tramite la codifica di Gray. La codifica di Gray rappresenta i
numeri binari in maniera tale che fra un numero e il successivo varia solo 1 bit.

193

194

CAPITOLO 23

PREDIZIONE DINAMICA
La previsione dinamica una previsione che viene fatta durante lesecuzione dellistruzione. E come fare
una scommessa basandosi su alcuni elementi.
Fino ad ora abbiamo visto la previsione statica con la schedulazione delle istruzioni fatta dal compilatore.
Prima dellesecuzione di un programma il compilatore compila ad esempio listruzione di branch con un
codice operativo che abortisca listruzione avviata dopo speculativamente se non si deve saltare, poich ha
previsto che il salto verr fatto un gran numero di volte. Quindi si ottimizza la compilazione.
La previsione dinamica invece consiste nel fare la scommessa durante lesecuzione del programma. Il
compito in questo caso pi agevole. Durante lesecuzione di un programma il calcolatore fa delle
previsioni sullistruzione di salto, ad esempio, se saltare o meno, e se saltare dove bisogna saltare. Queste
previsioni sono pi attendibili di quelle fate staticamente.
La previsione dinamica avviene basandosi sullunica cosa di cui si conosce di unistruzione quando la si
preleva. Lobbiettivo che al colpo di clock successivo al prelievo di un istruzione di salto, listruzione che
viene prelevata dopo deve essere quella corretta. Fino ad ora siamo riusciti a spostare il calcolo
dellindirizzo e il calcolo del test del branch nella fase di ID, e questo comporta come gi sappiano che il
fetch dellistruzione corretta avviene dopo due colpi di clock dopo il fetch dellistruzione di salto. La
prelevare listruzione corretta al colpo di clock successivo al fetch del branch.
Nellimmagine riportato prima quello che avviene esecuzione del programma, e sotto invece quello che
avviene con la predizione dinamica.

Il meccanismo della previsione dinamica si basa sulla storia di quello che successo fina a quel momento
nel programma. Perch questo avvenga nellunit di controllo del processore viene creata una struttura che
consente di sapere che cosa successo lultima volta che stata prelevata listruzione di salto. Parleremo
del prelievo di istruzioni di branch poich se listruzione non di branch ovvio che lindirizzo
dellistruzione dopo PC+4. Quindi se durante il fetch del branch si riesce a capire che la prossima
195

istruzione non quella PC+4 ma a quella in un certo indirizzo, si evita lo stallo. Lidea quella di prelevare il
branch e mentre si sta facendo PC+4, che avviene gi durante il fetch, questo viene sovrascritto con un PC
di salto: BTA Branch Target Address.
Nellunit di controllo presente una tabella con diverse informazioni. Durante il prelievo dellistruzione di
branch attraverso il suo PC, questo indirizzo in PC punta a questa tabella. Le informazioni fornite dalla
tabella sono:
-

se listruzione a quellindirizzo un istruzione di salto, prima ancora che questa venga prelevata
quindi sappiamo che questa un istruzione di salto. Lunit di controllo per come fa a sapere se
quellistruzione a quel indirizzo dato un istruzione di salto? Lunit di controllo sapr se
quellistruzione di salto o meno solo se stata gi eseguita prima (dalla storia del programma).
Infatti se stiamo in un loop, la prima volta che verr eseguita listruzione branch del loop, lunit di
controllo non la conoscer e andr a scriverla nella tabella, in modo tale che ciclando, alla prossima
iterazione quando andr a prelevare di nuovo quellistruzione sempre a quellindirizzo, avendola gi
in tabella sapr che quella un istruzione di salto.
Nella tabella c anche scritto lindirizzo di salto, dove si deve saltare.
Nella tabella c anche scritto qualcosa che aiuta a fare una previsione se il salto va fatto o meno.

Vediamo in dettaglio quello che accade.


Il PC punta a una riga della tabella, che quindi dovrebbe avere 232 righe, essendo il PC di 32bit. Ogni riga
deve avere dei bit per indicare lindirizzo dove saltare, quindi 32 bit; dei bit per indicare che listruzione di
salto, magari anche 1 bit; e dei bit designati ad indicare se il salto si far o meno.
Questa tabella dentro lunit di controllo. Nellunit di controllo non ha senso mettere una tabella di
queste dimensioni, di circa 16GB. Queste righe nella tabella sono di meno, ad esempio 512 righe. A questo
punto per puntare a una di queste righe, dai 32bit di indirizzo iniziale, ne servono solo 9, i meno significativi
in modo tale che due istruzioni successivi puntino sempre a righe diverse. Avendo scelto 9 bit c il
problema che una riga della tabella possa essere puntata da due istruzioni che hanno i 9 bit meno
significativi uguali, e quindi puntino entrambi alla stessa riga. Questo problema per non tragico se si crea

196

un meccanismo reversibile, bisogna preparare un filo di Arianna per ritornare indietro. E anche opportuno
che la tabella abbia una certa dimensione.
Quindi quello che avviene questo: si carica un istruzione a partire dallindirizzo. Gli n bit meno significativi
di questo indirizzo puntano a una riga della tabella, e si verifica se quella un istruzione di salto. Se non
un istruzione di salto non succede nulla. Come capire se questa o non un istruzione di salto? Si struttura
la tabella in modo tale da contenere solo istruzioni di salto, oppure si struttura in modo da avere solo
istruzioni che non sono di salto, e il campo dellindirizzo dove salta semplicemente PC +4, evitando di fare
PC+4.
Durante la fase di inizializzazione la tabella vuota. Man mano che si eseguono le istruzioni si riempie la
tabella. Si fa il fetch di una istruzione, e si punta alla riga della tabella; durante la fase di decode se questa
istruzione un branch si scrive nella tabella e si scrive anche lindirizzo dove saltare. Questa tabella viene
letta e poi aggiornata durante lesecuzione dellistruzione in modo che quando si accede di nuovo si legge il
valore aggiornato. Linformazione che si scrive un informazione che predice se il salto va eseguito o meno,
e questa previsione viene eseguita dal predittore.
1. Predittore a un bit
Per fare una previsione bisogna basarsi sulla storia del programma. Un meccanismo semplice quello di
utilizzare un bit che indica se lultima volta il salto stato effettuato o meno. Se lultima volta il salto stato
effettuato questo bit varr 1, 0 altrimenti. La predizione pu basarsi su questo meccanismo, se il bit vale 1
allora si salta, se il bit vale 0 non bisogna saltare. Se questa un previsione sullistruzione di branch posta in
un loop che itera N volte, questa previsione sar corretta N-2 volte. La prima volta in cui si entra nel loop
non avendo alcuna storia alle spalle, eseguendo per la prima volta listruzione di branch verr predetto che
non bisogner saltare, e invece il salto verr effettuato. Le restanti volte, venendo sempre da salti certi e
quindi bit pari a 1, verr predetto di saltare ed effettivamente si salter. Allultima iterazione invece verr
predetto di saltare, visto che la volta prima si saltato, per questa volta il salto non dovr farsi. Questo
meccanismo migliorabile con un predittore a 2bit.
2. Predittore a due bit
Il predittore a due bit tiene traccia di quello che successo non lultima volta, ma realizza una macchina a
stati.
Nelle reti combinatorie, dato un certo ingresso si ottiene una certa uscita funzione degli ingressi, y = f(x),
dove x e y possono essere dei vettori, un insieme di diversi bit. Esistono situazioni che non possono essere
descritte da questi tipo di sistema, poich per conoscere luscita di un sistema non possibile basarsi solo
sugli ingressi ma bisogna basarsi su quello che si chiama stato del sistema. Un classico esempio il
contatore che viene usato per descrivere un sistema la cui uscita non determinata in base al solo ingresso,
ma anche dallo stato del sistema. Dato un colpo di clock, il contatore non sapendo lo stato non potr dare
un uscita. Luscita di un contatore infatti data da s+1, se lingresso altro, dove s lo stato in cui si trova il
sistema prima del clock(evento da contare) che arriva. Il valore per non pu andare fino allinfinito, allora
si utilizza il modulo in modo che dopo un certo valore lo stato torni a zero. Nel realizzare questi sistemi il
progetto parte dal diagramma degli stati. Ci si piazza su uno stato e quando arrivano gli ingressi si decide
cosa fare. Supponiamo di avere solo due ingressi: con ingresso 0 ad esempio si passa un altro stato
producendo una certa uscita; se invece lingresso 1 si passa a un altro stato producendo un'altra uscita.
197

Esempio del diagramma degli stati del contatore modulo 8, quindi con 8 stati. Si chiamano gli stati con un
nome, esempio il numero. Si associa il nome allo stato con tecniche che ottimizzano le unit di memoria
che poi verranno usati per realizzare il sistema. Perch una macchina a stati necessita delle memorie(flipflop) per sapere il sistema in che stato si trova. Per ridurre luso di flip-flop ci sono delle tecniche, una
tecnica quella che gli stati vicini abbiano nomi vicini, utilizzando la codifica di Gray.
Supponiamo di essere sullo stato 0, il contatore sul display segna 0. Quando arriva un colpo di clock,
ingresso alto, valore 1, luscita vale 1 e poi lo stato passa a 1 (1/1, ingresso/uscita). Se invece lingresso
basso, luscita zero e si ritorna sullo stato zero (0/0).
Quindi se siamo nello stato 1 e arriva levento, si passa allo
stato 2 producendo un uscita 2, se invece non arriva alcun
evento, si resta sullo stato 1 producendo 1. E cos via.
Un contatore da zero a infinito necessitava di infiniti stati e
infiniti flip-flop.

Un predittore a due bit si realizza con una macchina a quattro stati: saltos, salton, nsaltos, nsalton.
Supponiamo di verificare se listruzione di salto deve saltare o no, e la storia del programma dice che le
ultime due volte siamo saltati.
Siamo nello stato saltos. In questo stato si predice di saltare. Se il salto viene effettivamente fatto, si resta
in questo stato (S/S, predico di saltare/salto effettuato). Se invece non si salta (S/N, predico di saltare/non
si salta), si passa allo stato salton. In questo stato si predice ancora di saltare. Se il salto viene
effettivamente fatto(S/S) si ritorna allo stato saltos. Se invece il salto non viene effettuato, si passa allo
stato nsalton. Nello stato salton si rimarr solo per un colpo. Nello stato nsalton, preveniamo da una storia
pari a 2 non salti. Non essendo saltati le ultime due volta, si predir di non saltare. Se effettivamente non si
effettua il salto (N/N), si rimane nello stato nsalton. Se invece predicendo di non saltare, e il salto si effettua
si passa allo stato nsaltos. In questo stato, lultima c stato il salto, ma almeno due volte prima non
abbiamo saltato, si continua a scommettere di non saltare. Se effettivamente non si salta (N/N), si ritorna
allo stato nsalton, altrimenti se si salta(N/S) si passa allo stato saltos, visto che adesso sono gi due le volte
in cui abbiamo saltato.

198

Quindi nel loop, arrivati allultima iterazione, si prevede di saltare, in realt il salto non si effettua. Si esce
dal loop. Quando poi si ritorna al loop, incontrando quellistruzione di branch, si predir di saltare, poich ci
troviamo nello stato salton. Effettivamente quellistruzione salta e la previsione andata a buon fine. Ora la
prima volta non si sbaglia. Si sbaglier per lultima volta. In un loop con N iterazioni, N-1 previsioni
saranno corrette.

199

200

CAPITOLO 24

ALGORITMO DI TOMASULO
Le prestazioni possono essere aumentate a livello di parallelismo di istruzioni (ILP), esecuzione parallelo di
pi istruzioni, attraverso alcuni aspetti:
-

Predizione di dinamica
Speculazione per permettere lesecuzione di istruzioni prima di risolvere eventuali dipendenze di
dati. Avviare in maniera speculativa istruzioni prima ancora di capire se quelle istruzioni devono
essere eseguite, sempre se poi possibile abortire nel caso in cui queste non devono essere
eseguite.

Per avere la possibilit di speculare bisogna pensare a un sistema in cui lesecuzione di un istruzione che
parte speculativamente, venga abortita nel caso in cui non debba essere eseguita. Si predispone di un
meccanismo che avvii istruzioni senza essere sicuri che queste debbano essere avviate, e che attraverso
una label permetti loro poi di scrivere effettivamente nei registri. Queste istruzioni potranno scrivere nei
registri solo nel momento in cui gli verr permesso farlo attraverso un commit. Facciamo lesempio di
istruzioni che devono operare su dati prodotti da un istruzione precedente: c listruzione load e un
istruzione che utilizza il dato prelevato con la load. Il compilatore metter unistruzione tra la load e
questultima istruzione per evitare lo stallo:
L.D

F8,(R1)0

DADD R4,(R4),8
ADD.D F6,F8,F2
La seconda istruzione inserita dal compilatore per evitare lo stallo prodotto dalla load. Questo per
ancora non garantisce che listruzione ADD.D che usa F8 possa tranquillamente utilizzare F8. In realt non
detto che la load in due colpi di clock possa estrarre F8, poich questo pu stare nellHARD DISK.
Listruzione ADD.D F6,F8,F2 comunque partir, per prima di scrivere F6 dovr ricevere il commit.
Listruzione load dovr dare una sorta di OK.
1. Buffer di Riordino
Il meccanismo di utilizzare dei buffer, buffer di riordino (ROB), che fungeranno da posteggi provvisori in
cui le istruzioni andranno a leggere e scrivere i dati, e solo quando listruzione otterr il commit questi
buffer verranno scritti nei registri. Il buffer di riordino una tabella con una struttura FIFO. Questo
meccanismo prende il nome di algoritmo di tomasulo, dal nome di chi lo ha sviluppato, il ricercatore
dellIBM Robert Tomasulo .
Il buffer di riordino utilizzato per passare i risultati fra le istruzioni schedulate speculate. Supponiamo di
avere questo programma:
L.D

F8,(R1)0

DADD R4,(R4),8
201

ADD.D F6,F8,F2
MULT.D F3,F5,F6
Se listruzione ADD.D non ha ancora ricevuto il commit, cio non ha ancora scritto F6, listruzione MULT.D
potr partire speculativamente, non prendendo il dato da F6 ma dal buffer di riordino. In questo modo non
si blocca nulla, si avviano istruzioni speculativamente.
Si crea una stazione di prenotazione, Reservation Station, per ciascun modulo di calcolo. Queste vengono
allocate dalle istruzioni e ad abilitarsi con i dati che non necessariamente vengono dai registri, ma instradati
dai buffer di riordino. Ad esempio listruzione MULT.D F3,F5,F6 si andr ad allocare nella stazione di
prenotazione, ma il dato F6 non verr preso dal registro F6, ma dal buffer di riordino associato allistruzione
ADD.D.
Il buffer di riordino ha questa struttura:
-

Tipo di istruzione: specifica oltre al tipo di operazione, anche il tipo di dato che quelloperazione sta
costruendo. Listruzione di branch che non costruisce nessun dato, produrr un risultato che un
indirizzo di salto. Listruzione di store ha come destinazione un indirizzo di memoria. Anche la store
non detto che venga scritto in un colpo di clock, pu darsi che la pagina su cui scrivere non in
cache e va caricata. E infine ci possono essere istruzioni aritmetiche, operazioni sui registri.
Destinazione: il registro dove dovr essere scritto il contenuto del buffer di riordino.
Valore: indica il contenuto del buffer di riordino. E quello che viene usato nellipotesi in cui come
nellesempio prima bisogna usare F6, senza che la ADD ha ricevuto il commit.
Ready: indica se listruzione terminata, ed allora possibile leggere il valore del buffer. Se il
campo Ready settato a zero, il valore che si legge nel buffer errato, quello relativo a qualche
istruzione precedente. Quindi Ready indica che il buffer ha un certo valore, quando poi listruzione
ricever il commit, questo valore verr scritto nel registro destinazione nel banco dei registri.

Ottimizzare lesecuzione del task facendo si che un istruzione potesse cominciare in maniera speculativa
anche nellipotesi in cui questa istruzione deve usare dati che non sono pronti poich generati da altre
istruzioni, oppure istruzioni che partono speculativamente a seguito di un branch. Tutto questi diventa
possibile se si mantiene la capacit di abortire istruzioni avviate speculativamente. Ogni istruzione pu
anche essere completata per il risultato non viene scritto nel banco dei registri. Si parte ugualmente
nellesecuzione di un istruzione e nel caso in cui questa non doveva essere eseguita viene abortita, oppure
a seguito di un branch viene caricata unistruzione e il risultato di questa viene posteggiato nel buffer che
pu comunque essere utilizzato da qualche altre istruzioni. Se listruzione viene confermata allora il buffer
relativo a quella istruzione viene scritto nel banco dei registri. Quindi si aggiunge una caratteristica alle
istruzioni, la caratteristica di essere commissionate. Unistruzione viene schedulata per non avr la
possibilit di scrivere il risultato nei registri finch non viene timbrato il flag di commit.
Quindi si parte speculativamente con una istruzione, si occupa una riga del buffer di riordino dove si andr
a scrivere il valore del risultato. Questo verr trascritto poi nel banco dei registri se listruzione verr
commissionata. Consideriamo le istruzioni:
MULT.D F4,F2,F8
ADD.D F5,F4,F9
202

Listruzione ADD.D potr leggere il campo F4 dal buffer di riordino, solo quando questo buffer per avr il
campo ready settato a uno ad indicare che il buffer pronto per essere utilizzato. Quando listruzione
MULT.D ricever il commit, scriver il valore del buffer in F4.
2. Fasi dellistruzione
Le fasi cambiano lievemente e diventano:
-

Fase di Issue: fase di licenziamento dellistruzione, in cui listruzione esce. Affinch un istruzione
entri nella fase di Issue, nel buffer di riordino deve esserci una linea libera, altrimenti se il buffer di
riordino satura bisogna aspettare.
Fase di Esecuzione: una volta che listruzione si prenotata nel buffer di riordino pu cominciare la
sua esecuzione, per listruzione deve avere gli operandi disponibili, che non sono necessariamente
nel banco dei registri, ma anche nel buffer di riordino di qualche altra istruzione ready.
Fase di Scrittura del risultato: terminata la fase di esecuzione, listruzione scrive il risultato nel
buffer di riordino attraverso un bus comune (CDB Common Data Bus). Quando lunit floating point
produce un risultato; questo risultato viene scritto nel buffer di riordino. Quando listruzione
ricever il commit il valore andr dal buffer di riordino nel registro. Quando un sommatore ha
scritto il risultato nel buffer di riordino, non va esclusivamente nel buffer ma anche nel bus
comune, in modo tale che se un altro sommatore ha bisogno di quel risultato, lo cattura dal bus e
non lo va a prendere dal buffer. Loperando si pu leggere dal buffer di riordino, oppure se non
ancora presente ci si pu mettere in ascolto sulla common data bus per prelevarlo non appena
questo stato prodotto da qualcuno. Quindi il risultato viene scritto sia sul CDB e sia sul buffer di
riordino. Quando poi un modulo floating point termina di lavorare marca quella stazione di
prenotazione come disponibile. Quando un istruzione va a mettersi in coda nel buffer di riordino
(issue), non solo bisogna trovare una riga disponibile nel buffer di riordino, ma bisogna anche
attendere la disponibilit dellunit funzionale che deve essere utilizzata. Bisogna quindi prenotarsi
sulle stazioni di prenotazione. Quando un istruzione termina, marca che la stazione di
prenotazione libera.
Fase di Commit: listruzione riceve il commit, avviene la scrittura del buffer di riordino nel buffer dei
registri.

Consideriamo ora come avviene lesecuzione di alcune istruzioni di un programma attraverso lalgoritmo di
Tomasulo.

203

Le istruzioni floating point nella coda di istruzioni aspettano


di esser inserite nel buffer di riordino, che ad esempio
contiene sette righe. Quando un istruzione entra nella fase
di issue, questa entra nel buffer di riordino che ricordiamo
essere di FIFO. Consideriamo ad esempio che la prima
istruzione ad entrare nel buffer di riordino LD F0,10(R2). Nel campo Ready(Done) c scritto N, listruzione
non stata completata. Nel campo destinazione c scritto F0, a dire che quella istruzione calcola F0.
Quando un istruzione va a mettersi in
coda nel buffer di riordino, listruzione
non solo deve trovare una riga libera nel
buffer di riordino, ma deve attendere la
disponibilit dellunit funzione che
deve andare a impegnare loperazione, e allora va a prenotare una riga della tabella stazione di
prenotazione. Nellimmagini sono rappresentate le stazioni di prenotazione per un sommatore,
moltiplicatore floating point e anche per laccesso a memoria. Quando poi un istruzione che ad esempio sta
effettuando una somma, termina di utilizzare il sommatore, marca la riga della stazione di prenotazione
relativa al sommatore come libera, quella stazione si liberata. Il buffer di riordino attende che listruzione
riceva il commit in modo tale da scrivere nel banco dei registri. Listruzione caricata prima nel buffer di
riordino la load, va prenotarsi nella stazione di prenotazione relativa allaccesso a memoria. Nella riga c
scritto nel primo campo il valore 1, ad indicare che si tratta dellistruzione al buffer di riordino 1,
linformazione al secondo campo invece indica che quellistruzione deve produrre un dato che sar letto
dalla memoria allindirizzo R2+10.
Al colpo di clock successivo, dalla coda istruzioni entra una nuova
istruzione nel buffer di riordino, ad esempio listruzione: ADDD
F10,F4,F0. Questa istruzione utilizza F0, il valore prodotto
204

dallistruzione load precedente. Questa utilizza il sommatore, e si prenota nella stazione di prenotazione.
Poich un suo sorgente F0, nella stazione di prenotazione c scritto che gli operandi dellistruzione,
relativa al buffer di riordino 2, sono uno in F4, banco dei registri, e laltro non disponibile, poich nel
buffer di riordino prima c F0 proprio sorgente di questa, allora il secondo operando sorgente va preso
quando sul CDB transiter il risultato dellistruzione del buffer di riordino 1, (ROB1). Non appena listruzione
al buffer di riordino 1 produce il risultato, questa spiando sul CDB lo vedr transitare e lo user per andare a
fare la somma.
Al colpo di clock successivo arriva la nuova istruzione, DIVD F2,F10,F6. Questa si va a prenotare nella
stazione di prenotazione relativa al divisore specificando che un operando va preso dal banco dei registri da
F6 mentre il primo va preso quando sul CDB transita il dato del buffer di riordino 2.
Al prossimo colpo di clock c listruzione BNE condizionata a F2 e noin ha nessuna destinazione.
Ci sono inoltre altre due istruzioni LD F4,0(R3) e ADDD F0,F4,F6 e il procedimento sempre quello
descritto.

Al colpo di clock successivo entra unistruzione di STORE: ST 0(R3),F4. Il sorgente della store F4 cio il
destinazione della load e quindi questo valore lo prende direttamente dl buffer di riordino 5. Questa
istruzione potrebbe creare un conflitto.
Meccanismo di eccezione: interrupt.

205

Le eccezioni e gli interrupt sono dei meccanismi che vengono controllati esternamente e tengono conto dei
problemi che dipendono dallesecuzione del programma. Questo problemi possono essere sincroni o
asincroni. Sono sincroni i fenomeni che avvengono sempre nello stesso momento quando il programma
viene eseguito con gli stessi dati. Sono asincroni i problemi che avvengono in maniera non prevedibile.
Colpo di clock per istruzione inferiore a uno.
Fare il fetch di un istruzione per colpo di clock, nella migliore delle ipotesi, il CPI=1, in generale CPI>=1. Si
pu migliorare il CPI con un sistema che licenzia un numero multiplo di istruzioni. Potrebbe essere il caso di
un processore superscalare, oppure di un processore VLIW in cui si esegue un pacchetto di 5 istruzione ad
esempio.
3. Esecuzione fuori ordine
Infine c la possibilit di utilizzare luso delle esecuzione fuori ordine. Lesecuzione delle istruzioni comincia
nellordine cos come sono scritte, per ci sono delle istruzioni pi lunghe di altre, perch utilizzano unit
funzionali pi lente, oppure perch richiedono dati che sono destinazioni di altre istruzioni devono
attendere che queste vengano prodotto, quindi risultano terminare dopo. Dinamicamente possibile
eseguire un programma che consente la terminazione fuori ordine attraverso lalgoritmo di Tomasulo. E
possibile gestire lesecuzione fuori ordine, le istruzioni vengono avviate e portate a termine, e questo pu
avvenire prima che un'altra istruzione precedente dal punto di vista logica sia terminata. Le istruzioni
vengono licenziate in ordine(issue), per le istruzioni terminano fuori ordine, cos come la scrittura dei
risultati. Questo pu funzionare con una unit di controllo che supervisiona laccesso alle unit verificando
che quando una istruzione prenota lunit deve avere anche informazioni sugli operandi da usare, e che se
non sono pronti non consente allistruzione di iniziare la fase di esecuzione sullunit floating point. Si
effettua solo uan prenotazione, lesecuzione comincia solo quando sono pronti tutti gli operandi.
Inoltre nelle stazioni di prenotazioni ci sono tante righe per quanti sono i moduli, ci sono 3 lane per
laccesso a memoria, allora ci sono 3 righe su cui prenotarsi sulla stazione di prenotazione per la memoria, e
cos via.
Un istruzione viene messa nel buffer di riordino nel momento in cui pu passare nella stazione di
prenotazione. Se non ci sono unit libere bisogna attendere.

206

Vous aimerez peut-être aussi